VMware vSphere Integrated Containers provider (#206)
* Add Virtual Kubelet provider for VIC Initial virtual kubelet provider for VMware VIC. This provider currently handles creating and starting of a pod VM via the VIC portlayer and persona server. Image store handling via the VIC persona server. This provider currently requires the feature/wolfpack branch of VIC. * Added pod stop and delete. Also added node capacity. Added the ability to stop and delete pod VMs via VIC. Also retrieve node capacity information from the VCH. * Cleanup and readme file Some file clean up and added a Readme.md markdown file for the VIC provider. * Cleaned up errors, added function comments, moved operation code 1. Cleaned up error handling. Set standard for creating errors. 2. Added method prototype comments for all interface functions. 3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder. * Add mocking code and unit tests for podcache, podcreator, and podstarter Used the unit test framework used in VIC to handle assertions in the provider's unit test. Mocking code generated using OSS project mockery, which is compatible with the testify assertion framework. * Vendored packages for the VIC provider Requires feature/wolfpack branch of VIC and a few specific commit sha of projects used within VIC. * Implementation of POD Stopper and Deleter unit tests (#4) * Updated files for initial PR
This commit is contained in:
1
vendor/github.com/vmware/vic/.dockerignore
generated
vendored
Normal file
1
vendor/github.com/vmware/vic/.dockerignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vagrant
|
||||
45
vendor/github.com/vmware/vic/.drone.local.yml
generated
vendored
Normal file
45
vendor/github.com/vmware/vic/.drone.local.yml
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/github.com/vmware/vic
|
||||
|
||||
pipeline:
|
||||
vic-integration-test-on-pr:
|
||||
image: '${TEST_BUILD_IMAGE=gcr.io/eminent-nation-87317/vic-integration-test:1.44}'
|
||||
pull: true
|
||||
environment:
|
||||
BIN: bin
|
||||
GOPATH: /go
|
||||
SHELL: /bin/bash
|
||||
LOG_TEMP_DIR: install-logs
|
||||
secrets:
|
||||
- bridge_network
|
||||
- debug_vch
|
||||
- drone_server
|
||||
- drone_token
|
||||
- drone_machine
|
||||
- github_automation_api_key
|
||||
- gs_client_email
|
||||
- gs_private_key
|
||||
- gs_project_id
|
||||
- public_network
|
||||
- registry_password
|
||||
- reporting_server_url
|
||||
- syslog_passwd
|
||||
- syslog_server
|
||||
- syslog_user
|
||||
- test_datastore
|
||||
- test_resource
|
||||
- test_timeout
|
||||
- test_password
|
||||
- test_url_array
|
||||
- test_username
|
||||
commands:
|
||||
- 'export GOVC_INSECURE=true'
|
||||
- 'export GOVC_USERNAME=${TEST_USERNAME}'
|
||||
- 'export GOVC_PASSWORD=${TEST_PASSWORD}'
|
||||
- 'export GOVC_DATASTORE=${TEST_DATASTORE}'
|
||||
- 'export DOMAIN=${DOMAIN}'
|
||||
- tests/integration-test.sh
|
||||
# - pybot -t "Delete VCH with non-cVM in same RP" tests/test-cases/Group6-VIC-Machine/6-03-Delete.robot
|
||||
volumes:
|
||||
- /tmp
|
||||
350
vendor/github.com/vmware/vic/.drone.yml
generated
vendored
Normal file
350
vendor/github.com/vmware/vic/.drone.yml
generated
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
|
||||
workspace:
|
||||
|
||||
base: /go
|
||||
path: src/github.com/vmware/vic
|
||||
|
||||
pipeline:
|
||||
|
||||
clone:
|
||||
image: plugins/git
|
||||
tags: true
|
||||
recursive: false
|
||||
|
||||
display-status:
|
||||
image: 'wdc-harbor-ci.eng.vmware.com/default-project/vic-integration-test:1.44'
|
||||
pull: true
|
||||
environment:
|
||||
BIN: bin
|
||||
SHELL: /bin/bash
|
||||
commands:
|
||||
- 'export COMMIT=${DRONE_COMMIT}'
|
||||
- 'echo "Expecting ${DRONE_COMMIT}"'
|
||||
- 'git log -5'
|
||||
- 'git log -1 --pretty=oneline | grep "^${DRONE_COMMIT}" > /dev/null && echo ''Build matches'' || (git log -1 --pretty=oneline | grep "Merge ${DRONE_COMMIT}" > /dev/null && echo ''Build is of a merge commit'' || (echo ''Build does not match!'' && exit 1))'
|
||||
|
||||
wait-for-build:
|
||||
image: 'wdc-harbor-ci.eng.vmware.com/default-project/vic-integration-test:1.44'
|
||||
pull: true
|
||||
environment:
|
||||
BIN: bin
|
||||
GOPATH: /go
|
||||
SHELL: /bin/bash
|
||||
secrets:
|
||||
- drone_server
|
||||
- drone_token
|
||||
- test_url_array
|
||||
commands:
|
||||
- tests/wait_until_previous_builds_complete.sh
|
||||
|
||||
check-org-membership:
|
||||
image: 'wdc-harbor-ci.eng.vmware.com/default-project/vic-integration-test:1.44'
|
||||
pull: true
|
||||
environment:
|
||||
BIN: bin
|
||||
GOPATH: /go
|
||||
SHELL: /bin/bash
|
||||
secrets:
|
||||
- github_automation_api_key
|
||||
commands:
|
||||
- echo ${DRONE_COMMIT_AUTHOR}
|
||||
- /bin/bash -c '[[ ! $(curl --silent "https://api.github.com/orgs/vmware/members/${DRONE_COMMIT_AUTHOR}?access_token=$GITHUB_AUTOMATION_API_KEY") ]]'
|
||||
when:
|
||||
status: success
|
||||
|
||||
vic-engine:
|
||||
image: 'wdc-harbor-ci.eng.vmware.com/default-project/vic-integration-test:1.44'
|
||||
pull: true
|
||||
environment:
|
||||
BIN: bin
|
||||
GOPATH: /go
|
||||
SHELL: /bin/bash
|
||||
secrets:
|
||||
- drone_server
|
||||
- drone_token
|
||||
- registry_password
|
||||
- test_password
|
||||
- test_url_array
|
||||
- test_username
|
||||
commands:
|
||||
- 'export BUILD_NUMBER=${DRONE_BUILD_NUMBER}'
|
||||
- 'export COMMIT=${DRONE_COMMIT}'
|
||||
- 'make mark'
|
||||
- 'make all'
|
||||
- 'make sincemark'
|
||||
- 'make mark'
|
||||
- 'echo `ls vendor/github.com/vmware/govmomi/vim25/methods`'
|
||||
- 'echo `ls vendor/github.com/vmware/govmomi/vim25/types`'
|
||||
- 'echo `ls vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system`'
|
||||
- 'export VIC_ESX_URL_ARRAY="`tests/get_test_url.sh`"'
|
||||
- tests/unit-test-check.sh
|
||||
- 'make sincemark'
|
||||
when:
|
||||
status: success
|
||||
|
||||
vic-integration-test-on-pr:
|
||||
image: 'wdc-harbor-ci.eng.vmware.com/default-project/vic-integration-test:1.44'
|
||||
pull: true
|
||||
privileged: true
|
||||
environment:
|
||||
BIN: bin
|
||||
GOPATH: /go
|
||||
SHELL: /bin/bash
|
||||
LOG_TEMP_DIR: install-logs
|
||||
secrets:
|
||||
- bridge_network
|
||||
- ci_domain
|
||||
- debug_vch
|
||||
- drone_server
|
||||
- drone_token
|
||||
- drone_machine
|
||||
- github_automation_api_key
|
||||
- gs_client_email
|
||||
- gs_private_key
|
||||
- gs_project_id
|
||||
- public_network
|
||||
- registry_password
|
||||
- reporting_server_url
|
||||
- syslog_passwd
|
||||
- syslog_server
|
||||
- syslog_user
|
||||
- test_datastore
|
||||
- test_resource
|
||||
- test_timeout
|
||||
- test_password
|
||||
- test_url_array
|
||||
- test_username
|
||||
commands:
|
||||
- export GOVC_INSECURE=true
|
||||
- export GOVC_USERNAME=$TEST_USERNAME
|
||||
- export GOVC_PASSWORD=$TEST_PASSWORD
|
||||
- export GOVC_DATASTORE=$TEST_DATASTORE
|
||||
- export DOMAIN=$CI_DOMAIN
|
||||
- echo $DOMAIN
|
||||
- 'make mark'
|
||||
- tests/integration-test.sh
|
||||
- 'make sincemark'
|
||||
volumes:
|
||||
- /tmp
|
||||
when:
|
||||
status: success
|
||||
|
||||
vic-ui:
|
||||
image: 'wdc-harbor-ci.eng.vmware.com/default-project/vic-integration-test:1.44'
|
||||
pull: true
|
||||
environment:
|
||||
BIN: bin
|
||||
secrets:
|
||||
- gs_client_email
|
||||
- gs_private_key
|
||||
- gs_project_id
|
||||
commands:
|
||||
- 'export BUILD_NUMBER=${DRONE_BUILD_NUMBER}'
|
||||
- 'mkdir -p $BIN/ui'
|
||||
- 'export LATEST_VIC_UI_BUILD="$(gsutil ls -l ''gs://vic-ui-builds'' | grep -v TOTAL | grep vic_ | sort -k2 -r | (trap '' '' PIPE; head -1))"'
|
||||
- 'export VIC_UI_URL=$(echo $LATEST_VIC_UI_BUILD | xargs | cut -d " " -f 3 | sed "s|gs://|https://storage.googleapis.com/|")'
|
||||
- 'wget -nv $VIC_UI_URL -P $BIN'
|
||||
- 'tar -xvzf $BIN/vic_ui_*.tar.gz'
|
||||
- 'ls -la ./$BIN && ./$BIN/ui/sync-vic-ui-version.sh -p bin/ 2>&1'
|
||||
- 'rm $BIN/vic_ui_*.tar.gz'
|
||||
when:
|
||||
status: success
|
||||
branch: [master]
|
||||
|
||||
vic-ui-release:
|
||||
image: 'wdc-harbor-ci.eng.vmware.com/default-project/vic-integration-test:1.44'
|
||||
pull: true
|
||||
environment:
|
||||
BIN: bin
|
||||
secrets:
|
||||
- gs_client_email
|
||||
- gs_private_key
|
||||
- gs_project_id
|
||||
commands:
|
||||
- 'export BUILD_NUMBER=${DRONE_BUILD_NUMBER}'
|
||||
- 'mkdir -p $BIN/ui'
|
||||
- 'export LATEST_VIC_UI_RELEASE="$(gsutil ls -l ''gs://vic-ui-releases'' | grep -v TOTAL | grep vic_ | sort -k2 -r | (trap '' '' PIPE; head -1))"'
|
||||
- 'export VIC_UI_URL=$(echo $LATEST_VIC_UI_RELEASE | xargs | cut -d " " -f 3 | sed "s|gs://|https://storage.googleapis.com/|")'
|
||||
- 'wget -nv $VIC_UI_URL -P $BIN'
|
||||
- 'tar -xvzf $BIN/vic_ui_*.tar.gz'
|
||||
- 'ls -la ./$BIN && ./$BIN/ui/sync-vic-ui-version.sh -p bin/ 2>&1'
|
||||
- 'rm $BIN/vic_ui_*.tar.gz'
|
||||
when:
|
||||
status: success
|
||||
branch: ['releases/*', 'refs/tags/*']
|
||||
|
||||
bundle:
|
||||
image: 'gcr.io/eminent-nation-87317/golang:1.8'
|
||||
pull: true
|
||||
environment:
|
||||
BIN: bin
|
||||
BIN_TEMP_DIR: bin/vic
|
||||
GOPATH: /go
|
||||
SHELL: /bin/bash
|
||||
commands:
|
||||
- 'make mark'
|
||||
- 'rm -rf $BIN_TEMP_DIR'
|
||||
- 'mkdir -p $BIN_TEMP_DIR'
|
||||
- 'mv $BIN/ui $BIN_TEMP_DIR'
|
||||
- 'cp LICENSE $BIN_TEMP_DIR'
|
||||
- 'cp doc/bundle/README $BIN_TEMP_DIR'
|
||||
- 'cp $BIN/vic-machine* $BIN_TEMP_DIR'
|
||||
- 'cp $BIN/vic-ui* $BIN_TEMP_DIR'
|
||||
- 'cp $BIN/appliance.iso $BIN_TEMP_DIR'
|
||||
- 'cp $BIN/bootstrap.iso $BIN_TEMP_DIR'
|
||||
- 'tar czvf $BIN/vic_${DRONE_BUILD_NUMBER}.tar.gz -C $BIN vic'
|
||||
- 'shasum -a 256 $BIN/vic_${DRONE_BUILD_NUMBER}.tar.gz'
|
||||
- 'shasum -a 1 $BIN/vic_${DRONE_BUILD_NUMBER}.tar.gz'
|
||||
- 'md5sum $BIN/vic_${DRONE_BUILD_NUMBER}.tar.gz'
|
||||
- 'du -ks $BIN/vic_${DRONE_BUILD_NUMBER}.tar.gz | awk ''{print $1 / 1024}'' | { read x; echo $x MB; }'
|
||||
- 'mkdir bundle'
|
||||
- 'mkdir bundle-release'
|
||||
- 'cp $BIN/vic_${DRONE_BUILD_NUMBER}.tar.gz bundle'
|
||||
- 'cp $BIN/vic_${DRONE_BUILD_NUMBER}.tar.gz bundle-release/vic_`git describe --tags $(git rev-list --tags --max-count=1)`.tar.gz'
|
||||
- 'make sincemark'
|
||||
when:
|
||||
repo: vmware/vic
|
||||
event: [push, tag]
|
||||
branch: [master, 'releases/*', 'refs/tags/*']
|
||||
|
||||
publish-gcs-builds-on-pass:
|
||||
image: 'victest/drone-gcs:1'
|
||||
pull: true
|
||||
secrets:
|
||||
- google_key
|
||||
source: bundle
|
||||
target: vic-engine-builds
|
||||
acl:
|
||||
- 'allUsers:READER'
|
||||
cache_control: 'public,max-age=3600'
|
||||
when:
|
||||
repo: vmware/vic
|
||||
event: [push]
|
||||
branch: [master, 'releases/*']
|
||||
status: success
|
||||
|
||||
publish-gcs-builds-on-fail:
|
||||
image: 'victest/drone-gcs:1'
|
||||
pull: true
|
||||
secrets:
|
||||
- google_key
|
||||
source: bundle
|
||||
target: vic-engine-failed-builds
|
||||
acl:
|
||||
- 'allUsers:READER'
|
||||
cache_control: 'public,max-age=3600'
|
||||
when:
|
||||
repo: vmware/vic
|
||||
event: [push]
|
||||
branch: [master, 'releases/*']
|
||||
status: failure
|
||||
|
||||
publish-gcs-releases:
|
||||
image: 'victest/drone-gcs:1'
|
||||
pull: true
|
||||
secrets:
|
||||
- google_key
|
||||
source: bundle-release
|
||||
target: vic-engine-releases
|
||||
acl:
|
||||
- 'allUsers:READER'
|
||||
cache_control: 'public,max-age=3600'
|
||||
when:
|
||||
repo: vmware/vic
|
||||
event: [push, tag]
|
||||
branch: ['refs/tags/*']
|
||||
status: success
|
||||
|
||||
publish-vic-machine-server-dev:
|
||||
image: plugins/gcr
|
||||
repo: eminent-nation-87317/vic-machine-server
|
||||
dockerfile: cmd/vic-machine-server/Dockerfile
|
||||
secrets:
|
||||
- gs_client_email
|
||||
- gs_private_key
|
||||
- gs_private_key_id
|
||||
- gs_project_id
|
||||
tags:
|
||||
- dev
|
||||
json_key: "{ \"type\": \"service_account\",\n \"project_id\": \"$GS_PROJECT_ID\",\n \"private_key_id\": \"$GS_PRIVATE_KEY_ID\",\n \"private_key\": \"$GS_PRIVATE_KEY\",\n \"client_email\": \"$GS_CLIENT_EMAIL\",\n \"client_id\": \"$GS_PROJECT_ID\",\n \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n \"token_uri\": \"https://accounts.google.com/o/oauth2/token\",\n \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\n \"client_x509_cert_url\": \"\"\n}\n"
|
||||
when:
|
||||
repo: vmware/vic
|
||||
event: [push]
|
||||
branch: [master]
|
||||
status: success
|
||||
|
||||
publish-vic-machine-server-releases:
|
||||
image: plugins/gcr
|
||||
repo: eminent-nation-87317/vic-machine-server
|
||||
dockerfile: cmd/vic-machine-server/Dockerfile
|
||||
secrets:
|
||||
- gs_client_email
|
||||
- gs_private_key
|
||||
- gs_private_key_id
|
||||
- gs_project_id
|
||||
tags:
|
||||
- latest
|
||||
json_key: "{ \"type\": \"service_account\",\n \"project_id\": \"$GS_PROJECT_ID\",\n \"private_key_id\": \"$GS_PRIVATE_KEY_ID\",\n \"private_key\": \"$GS_PRIVATE_KEY\",\n \"client_email\": \"$GS_CLIENT_EMAIL\",\n \"client_id\": \"$GS_PROJECT_ID\",\n \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n \"token_uri\": \"https://accounts.google.com/o/oauth2/token\",\n \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\n \"client_x509_cert_url\": \"\"\n}\n"
|
||||
when:
|
||||
repo: vmware/vic
|
||||
event: [push, tag]
|
||||
branch: ['refs/tags/*', 'releases/*']
|
||||
status: success
|
||||
|
||||
trigger-downstream:
|
||||
image: 'gcr.io/eminent-nation-87317/vic-downstream-trigger:1.0'
|
||||
environment:
|
||||
SHELL: /bin/bash
|
||||
secrets:
|
||||
- drone_server
|
||||
- drone_token
|
||||
when:
|
||||
repo: vmware/vic
|
||||
event: [push, tag]
|
||||
branch: [master, 'releases/*', 'refs/tags/*']
|
||||
status: success
|
||||
|
||||
report-coverage:
|
||||
image: robertstettner/drone-codecov
|
||||
secrets:
|
||||
- codecov_token
|
||||
files:
|
||||
- .cover/cover.out
|
||||
|
||||
notify-slack-on-fail:
|
||||
image: plugins/slack
|
||||
secrets:
|
||||
- source: slack_url
|
||||
target: slack_webhook
|
||||
username: drone
|
||||
template: "Build https://ci-vic.vmware.com/vmware/vic/{{ build.number }} by {{ build.author }} finished with a {{ build.status }} status. Logs: https://console.cloud.google.com/m/cloudstorage/b/vic-ci-logs/o/integration_logs_{{ build.number }}_{{ build.commit }}.zip?authuser=1\n"
|
||||
when:
|
||||
repo: vmware/vic
|
||||
event: [push, tag, deployment]
|
||||
branch: [master, 'releases/*', 'refs/tags/*']
|
||||
status: failure
|
||||
|
||||
notify-slack-on-pass:
|
||||
image: plugins/slack
|
||||
secrets:
|
||||
- source: slack_url
|
||||
target: slack_webhook
|
||||
username: drone
|
||||
template: "Build https://ci-vic.vmware.com/vmware/vic/{{ build.number }} by {{ build.author }} finished with a {{ build.status }} status, find the build at: https://storage.googleapis.com/vic-engine-builds/vic_{{ build.number }}.tar.gz\n"
|
||||
when:
|
||||
repo: vmware/vic
|
||||
event: [push, tag, deployment]
|
||||
branch: [master, 'releases/*']
|
||||
status: success
|
||||
|
||||
notify-slack-on-successful-tag:
|
||||
image: plugins/slack
|
||||
secrets:
|
||||
- source: slack_url
|
||||
target: slack_webhook
|
||||
username: drone
|
||||
template: "The latest version of VIC engine has been released, find the build here: https://console.cloud.google.com/storage/browser/vic-engine-releases\n"
|
||||
when:
|
||||
repo: vmware/vic
|
||||
event: [push, tag, deployment]
|
||||
branch: ['refs/tags/*']
|
||||
53
vendor/github.com/vmware/vic/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
53
vendor/github.com/vmware/vic/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
<!--
|
||||
Issues in this repository are for VIC Engine only
|
||||
|
||||
Issues related to other components should be submitted to their respective projects:
|
||||
VIC Appliance (OVA): https://github.com/vmware/vic-product/issues
|
||||
Container Management Portal (Admiral): https://github.com/vmware/admiral/issues
|
||||
Container Registry (Harbor): https://github.com/vmware/harbor/issues
|
||||
|
||||
If you are unsure which component your issue relates to, submit it to https://github.com/vmware/vic-product/issues and we will triage it
|
||||
|
||||
Thank you for contributing to VIC Engine! Please review this checklist before submitting your issue.
|
||||
|
||||
[ ] Search for duplicates before logging new issues
|
||||
https://github.com/vmware/vic/issues?q=is%3Aopen+is%3Aissue
|
||||
[ ] Questions are best asked and answered on Slack
|
||||
https://vmwarecode.slack.com/messages/vic-engine (see https://github.com/vmware/vic/blob/master/CONTRIBUTING.md#community for details)
|
||||
-->
|
||||
|
||||
#For stories, please include the information below:
|
||||
|
||||
**User Statement:**
|
||||
|
||||
A brief statement describe who, what and why of the story for example - As a job seeker, I want to search for a job, so I can advance my career.
|
||||
|
||||
**Details:**
|
||||
Any details of what you want that might clarify for the developer how to approach the implementation.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
Specific deliverable and actionable items that the story needs to deliver in order to be considered complete, the more detail here the more accurate the implementation will be.
|
||||
|
||||
|
||||
#For bug reports, please include the information below:
|
||||
|
||||
**VIC version:**
|
||||
|
||||
x.y.z / master / commit ID
|
||||
|
||||
**Deployment details:**
|
||||
|
||||
What was the vic-machine create command used to deploy the VCH?
|
||||
|
||||
**Steps to reproduce:**
|
||||
|
||||
**Actual behavior:**
|
||||
|
||||
**Expected behavior:**
|
||||
|
||||
**Logs:**
|
||||
|
||||
* If you can access the VCH Admin portal, please download and attach the log bundle(s). See https://vmware.github.io/vic/assets/files/html/vic_admin/log_bundles.html for details.
|
||||
* If the VCH Admin portal is inaccessible, you can enable SSH to the VCH endpoint VM to obtain logs manually. See https://vmware.github.io/vic/assets/files/html/vic_admin/vch_ssh_access.html for details. The VCH logs will be under /var/log/vic/ on the VM.
|
||||
|
||||
**Additional details as necessary:**
|
||||
26
vendor/github.com/vmware/vic/.github/PULL_REQUEST_TEMPLATE.md
generated
vendored
Normal file
26
vendor/github.com/vmware/vic/.github/PULL_REQUEST_TEMPLATE.md
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
Thank you for submitting a pull request!
|
||||
|
||||
Here's a checklist you might find useful.
|
||||
[ ] There is an associated issue that is labelled
|
||||
[ ] Code is up-to-date with the `master` branch
|
||||
[ ] You've successfully run `make test` locally
|
||||
[ ] There are new or updated unit tests validating the change
|
||||
|
||||
Refer to CONTRIBUTING.MD for more details.
|
||||
https://github.com/vmware/vic/blob/master/.github/CONTRIBUTING.md
|
||||
-->
|
||||
|
||||
Fixes #
|
||||
|
||||
<!--
|
||||
To trigger a custom build with this PR, include one of these in the PR's body:
|
||||
- To skip running tests (e.g. for a work-in-progress PR), add `[ci skip]` or `[skip ci]`
|
||||
to the commit message or the PR title.
|
||||
- To run the full test suite, use `[full ci]`.
|
||||
- To run _one_ integration test or group, use `[specific ci=$test]`. Examples:
|
||||
- To run the `1-01-Docker-Info` suite: `[specific ci=1-01-Docker-Info]`
|
||||
- To run all suites under the `Group1-Docker-Commands` group: `[specific ci=Group1-Docker-Commands]`
|
||||
- To skip running the unit tests, use `[skip unit]`.
|
||||
- To fail fast (make normal failures fatal) during the integration testing, use `[fast fail]`.
|
||||
-->
|
||||
50
vendor/github.com/vmware/vic/.gitignore
generated
vendored
Normal file
50
vendor/github.com/vmware/vic/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
.vagrant/
|
||||
.vscode/
|
||||
.cover/
|
||||
bin/
|
||||
binary/
|
||||
vic_binaries*.tar.gz
|
||||
secrets.yml
|
||||
test_secrets.yml
|
||||
nightly_ui_tests_secrets.yml
|
||||
secrets-e2e.yml
|
||||
vic-ci-logs.key
|
||||
install-*.tar.gz
|
||||
*.log
|
||||
log.html
|
||||
report.html
|
||||
*.xml
|
||||
*.gas
|
||||
*.pyc
|
||||
package.list
|
||||
*container-logs.zip
|
||||
*certs.zip
|
||||
integration_logs*
|
||||
vmware-ovftool
|
||||
|
||||
# generally a bad idea to check certificates into repos - whitelist if needed
|
||||
**/*.pem
|
||||
|
||||
lib/apiservers/portlayer/cmd
|
||||
lib/apiservers/portlayer/restapi/*.go
|
||||
lib/apiservers/portlayer/restapi/operations
|
||||
!lib/apiservers/portlayer/restapi/configure_port_layer.go
|
||||
lib/config/dynamic/admiral/client
|
||||
lib/config/dynamic/admiral/models
|
||||
lib/config/dynamic/admiral/operations
|
||||
|
||||
lib/apiservers/service/models
|
||||
lib/apiservers/service/restapi/*.go
|
||||
lib/apiservers/service/restapi/operations
|
||||
!lib/apiservers/service/restapi/configure_vic_machine.go
|
||||
|
||||
tests/.project
|
||||
# go test binaries
|
||||
*.test
|
||||
|
||||
# Intellij files
|
||||
*.iml
|
||||
|
||||
# Eclipse files
|
||||
.project
|
||||
|
||||
260
vendor/github.com/vmware/vic/.pullapprove.yml
generated
vendored
Normal file
260
vendor/github.com/vmware/vic/.pullapprove.yml
generated
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
version: 2
|
||||
group_defaults:
|
||||
required: 1
|
||||
author_approval:
|
||||
auto: true
|
||||
approve_by_comment:
|
||||
enabled: true
|
||||
approve_regex: 'Approved|:shipit:|:sheep::it:|:\+1:|LGTM|lgtm'
|
||||
reset_on_push:
|
||||
enabled: false
|
||||
|
||||
groups:
|
||||
contributors:
|
||||
required: 3
|
||||
teams:
|
||||
- vic-maintainers
|
||||
|
||||
tether:
|
||||
users:
|
||||
- hickeng
|
||||
- vburenin
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "cmd/tether/*"
|
||||
- "lib/tether/*"
|
||||
|
||||
build-ci-infra:
|
||||
users:
|
||||
- mhagen-vmware
|
||||
- rajanashok
|
||||
- rogeliosanchez
|
||||
- mdharamadas1
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "demos/*"
|
||||
- "infra/*"
|
||||
- "tests/*"
|
||||
|
||||
design-docs:
|
||||
users:
|
||||
- hickeng
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "doc/design/*"
|
||||
|
||||
docs:
|
||||
users:
|
||||
- stuclem
|
||||
- mdubya66
|
||||
- alextopuzov
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "doc/*"
|
||||
exclude:
|
||||
- "doc/design/*"
|
||||
- "doc/bundle/*"
|
||||
|
||||
nightlies:
|
||||
users:
|
||||
- rajanashok
|
||||
- mhagen-vmware
|
||||
- rogeliosanchez
|
||||
- mdharamadas1
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "tests/nightly/*"
|
||||
|
||||
log:
|
||||
users:
|
||||
- cgtexmex
|
||||
- zjs
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "pkg/log/*"
|
||||
|
||||
docker-persona:
|
||||
users:
|
||||
- sflxn
|
||||
- cgtexmex
|
||||
- jzt
|
||||
- vburenin
|
||||
- anchal-agrawal
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "lib/apiservers/engine/*"
|
||||
- "cmd/docker/*"
|
||||
exclude:
|
||||
- "lib/apiservers/portlayer/*"
|
||||
|
||||
install:
|
||||
users:
|
||||
- zjs
|
||||
- anchal-agrawal
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "cmd/vic-machine/*"
|
||||
- "lib/install/*"
|
||||
upgrade:
|
||||
users:
|
||||
- zjs
|
||||
- anchal-agrawal
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "lib/migration/*"
|
||||
|
||||
vic-machine-service:
|
||||
users:
|
||||
- zjs
|
||||
- AngieCris
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "cmd/vic-machine-server/*"
|
||||
- "lib/apiservers/service/*"
|
||||
|
||||
config:
|
||||
users:
|
||||
- hickeng
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "lib/config/*"
|
||||
- "lib/metadata/*"
|
||||
|
||||
vicadmin:
|
||||
users:
|
||||
- jzt
|
||||
- gigawhitlocks
|
||||
- sgairo
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "cmd/vicadmin/*"
|
||||
- "lib/vicadmin/*"
|
||||
- "isos/vicadmin/*"
|
||||
|
||||
port-layer-exec-interaction:
|
||||
users:
|
||||
- cgtexmex
|
||||
- hickeng
|
||||
- matthewavery
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "lib/portlayer/attach/*"
|
||||
- "lib/portlayer/exec/*"
|
||||
- "lib/portlayer/exec2/*"
|
||||
|
||||
port-layer-storage:
|
||||
users:
|
||||
- jzt
|
||||
- matthewavery
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "lib/portlayer/storage/*"
|
||||
|
||||
port-layer-store:
|
||||
users:
|
||||
- cgtexmex
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "lib/portlayer/store/*"
|
||||
|
||||
|
||||
package-kv-store:
|
||||
users:
|
||||
- jzt
|
||||
- matthewavery
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "pkg/kvstore/*"
|
||||
|
||||
port-layer-network:
|
||||
users:
|
||||
- hmahmood
|
||||
- hickeng
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "cmd/vic-dns/*"
|
||||
- "lib/dns/*"
|
||||
- "lib/dhcp/*"
|
||||
- "lib/portlayer/network/*"
|
||||
|
||||
events:
|
||||
users:
|
||||
- cgtexmex
|
||||
- hickeng
|
||||
- dougm
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "lib/portlayer/event/*"
|
||||
|
||||
imagec:
|
||||
users:
|
||||
- jzt
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "lib/imagec/*"
|
||||
|
||||
vendor:
|
||||
users:
|
||||
- mdubya66
|
||||
- dougm
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "vendor/*"
|
||||
ui:
|
||||
users:
|
||||
- jooskim
|
||||
- cristianfalcone
|
||||
- jak-atx
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "ui/*"
|
||||
|
||||
ui-cmd:
|
||||
users:
|
||||
- andrewtchin
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "cmd/vic-ui/*"
|
||||
|
||||
pullapprove:
|
||||
users:
|
||||
- gigawhitlocks
|
||||
- anchal-agrawal
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- ".pullapprove.yml"
|
||||
|
||||
drone:
|
||||
users:
|
||||
- jakedsouza
|
||||
- rajanashok
|
||||
- mhagen-vmware
|
||||
- mdharamadas1
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- ".drone.yml"
|
||||
- ".drone.sec"
|
||||
281
vendor/github.com/vmware/vic/CONTRIBUTING.md
generated
vendored
Normal file
281
vendor/github.com/vmware/vic/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
# Contributing to VIC Engine
|
||||
|
||||
## Community
|
||||
|
||||
In addition to using the GitHub issue tracker, contributors and users are encouraged to collaborate using the following
|
||||
resources:
|
||||
|
||||
- [Slack](https://vmwarecode.slack.com/messages/vic-engine): This is the primary community channel. **If you don't have
|
||||
an @vmware.com or @emc.com email, please sign up at https://code.vmware.com/join to get a Slack invite.**
|
||||
|
||||
- [Gitter](https://gitter.im/vmware/vic): Gitter is monitored, but please use the Slack channel if you need a response
|
||||
quickly.
|
||||
|
||||
## Getting started
|
||||
|
||||
First, fork the repository on GitHub to your personal account.
|
||||
|
||||
Note that _GOPATH_ can be any directory, the example below uses _$HOME/vic_.
|
||||
Change _$USER_ below to your GitHub username.
|
||||
|
||||
``` shell
|
||||
export GOPATH=$HOME/vic
|
||||
mkdir -p $GOPATH/src/github.com/vmware
|
||||
go get github.com/vmware/vic
|
||||
cd $GOPATH/src/github.com/vmware/vic
|
||||
git config push.default nothing # anything to avoid pushing to vmware/vic by default
|
||||
git remote rename origin vmware
|
||||
git remote add $USER git@github.com:$USER/vic.git
|
||||
git fetch $USER
|
||||
```
|
||||
|
||||
See the [README](README.md#building) for build instructions.
|
||||
|
||||
## Contribution flow
|
||||
|
||||
This is a rough outline of what a contributor's workflow looks like:
|
||||
|
||||
- Create a topic branch from where you want to base your work.
|
||||
- Make commits of logical units.
|
||||
- Make sure your commit messages are in the proper format (see below).
|
||||
- Push your changes to a topic branch in your fork of the repository.
|
||||
- Test your changes as detailed in the [Automated Testing](#automated-testing) section.
|
||||
- Submit a pull request to vmware/vic.
|
||||
- Your PR must receive approvals from component owners and at least two approvals overall from maintainers before merging.
|
||||
|
||||
Example:
|
||||
|
||||
``` shell
|
||||
git checkout -b my-new-feature vmware/master
|
||||
git commit -a
|
||||
git push $USER my-new-feature
|
||||
```
|
||||
|
||||
### Stay in sync with upstream
|
||||
|
||||
When your branch gets out of sync with the vmware/master branch, use the following to update it:
|
||||
|
||||
``` shell
|
||||
git checkout my-new-feature
|
||||
git fetch -a
|
||||
git rebase vmware/master
|
||||
git push --force-with-lease $USER my-new-feature
|
||||
```
|
||||
|
||||
### Updating pull requests
|
||||
|
||||
If your PR fails to pass CI or needs changes based on code review, you'll most likely want to squash these changes into
|
||||
existing commits.
|
||||
|
||||
If your pull request contains a single commit or your changes are related to the most recent commit, you can simply
|
||||
amend the commit.
|
||||
|
||||
``` shell
|
||||
git add .
|
||||
git commit --amend
|
||||
git push --force-with-lease $USER my-new-feature
|
||||
```
|
||||
|
||||
If you need to squash changes into an earlier commit, you can use:
|
||||
|
||||
``` shell
|
||||
git add .
|
||||
git commit --fixup <commit>
|
||||
git rebase -i --autosquash vmware/master
|
||||
git push --force-with-lease $USER my-new-feature
|
||||
```
|
||||
|
||||
Be sure to add a comment to the PR indicating your new changes are ready to review, as GitHub does not generate a
|
||||
notification when you git push.
|
||||
|
||||
### Code style
|
||||
|
||||
VIC Engine uses the coding style suggested by the Golang community. See the
|
||||
[style doc](https://github.com/golang/go/wiki/CodeReviewComments) for details.
|
||||
|
||||
Try to limit column width to 120 characters for both code and markdown documents such as this one.
|
||||
|
||||
### Format of the Commit Message
|
||||
|
||||
We follow the conventions on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/).
|
||||
|
||||
Be sure to include any related GitHub issue references in the commit message. See
|
||||
[GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) for referencing issues and
|
||||
commits.
|
||||
|
||||
To help write conforming commit messages, we recommend setting up the [git-good-commit][commithook] commit hook. Run this
|
||||
command in the VIC repo's root directory:
|
||||
|
||||
```shell
|
||||
curl https://cdn.rawgit.com/tommarshall/git-good-commit/v0.6.1/hook.sh > .git/hooks/commit-msg && chmod +x .git/hooks/commit-msg
|
||||
```
|
||||
|
||||
[dronevic]:https://ci-vic.vmware.com/vmware/vic
|
||||
[dronesrc]:https://github.com/drone/drone
|
||||
[dronecli]:http://readme.drone.io/0.5/install/cli/
|
||||
[commithook]:https://github.com/tommarshall/git-good-commit
|
||||
|
||||
## Automated Testing
|
||||
|
||||
Automated testing uses [Drone][dronesrc].
|
||||
|
||||
Pull requests must pass unit tests and integration tests before being merged into the master branch. A standard PR builds
|
||||
the project and runs unit and regression tests. To customize the integration test suite that runs in your pull request,
|
||||
you can use these keywords in your PR body:
|
||||
|
||||
- To skip running tests (e.g. for a work-in-progress PR), use `[ci skip]` or `[skip ci]`.
|
||||
- To run the full test suite, use `[full ci]`.
|
||||
- To run _one_ integration test or group, use `[specific ci=$test]`. This will run the regression test as well. Examples:
|
||||
- To run the `1-01-Docker-Info` suite: `[specific ci=1-01-Docker-Info]`
|
||||
- To run all suites under the `Group1-Docker-Commands` group: `[specific ci=Group1-Docker-Commands]`
|
||||
- To skip running the unit tests, use `[skip unit]`.
|
||||
- To fail fast (make normal failures fatal) during the integration testing, use `[fast fail]`.
|
||||
|
||||
You can run the tests locally before making a PR or view the Drone build results for [unit tests and integration tests][dronevic].
|
||||
|
||||
If you don't have a running ESX required for tests, you can leverage the automated Drone servers for
|
||||
running tests. Add `WIP` (work in progress) to the PR title to alert reviewers that the PR is not ready to be merged.
|
||||
|
||||
If your Drone build needs to be restarted, fork the build:
|
||||
```shell
|
||||
export DRONE_TOKEN=<Drone Token>
|
||||
export DRONE_SERVER=https://ci-vic.vmware.com
|
||||
|
||||
drone build start vmware/vic <Build Number>
|
||||
```
|
||||
|
||||
### Testing locally
|
||||
|
||||
Developers need to install [Drone CLI][dronecli].
|
||||
|
||||
#### Unit tests
|
||||
|
||||
``` shell
|
||||
VIC_ESX_TEST_URL="<USER>:<PASS>@<ESX IP>" drone exec .drone.yml
|
||||
```
|
||||
|
||||
If you don't have a running ESX, tests requiring an ESX can be skipped with the following:
|
||||
|
||||
``` shell
|
||||
drone exec
|
||||
```
|
||||
|
||||
#### Integration tests
|
||||
|
||||
Integration tests require a running ESX on which to deploy VIC Engine. See [VIC Integration & Functional Test Suite](tests/README.md).
|
||||
|
||||
## Reporting Bugs and Creating Issues
|
||||
|
||||
When opening a new issue, try to roughly follow the commit message format conventions above.
|
||||
|
||||
We use [Zenhub](https://www.zenhub.io/) for project management on top of GitHub issues. Once you have the Zenhub
|
||||
browser plugin installed, click on the [Boards](https://github.com/vmware/vic/issues#boards) tab to open the Zenhub task
|
||||
board.
|
||||
|
||||
Our task board practices are as follows:
|
||||
|
||||
### New Issues
|
||||
|
||||
The New Issues are triaged by the team at least once a week. We try to keep issues from staying in this pipeline for
|
||||
too long. After triaging and issue, it will likely be moved to the backlog or stay under [Not Ready](#not-ready) for deferred
|
||||
discussion.
|
||||
|
||||
For VIC engineers, you should set the priority based on the below guidelines. Everyone else, do not set the priority of a new issue.
|
||||
|
||||
#### Priorities
|
||||
|
||||
| Priority | Bugs | Features | Non Bugs |
|
||||
| -------- | ---- | -------- | -------- |
|
||||
| priority/p0 | Bugs that NEED to be fixed immediately as they either block meaningful testing or are release stoppers for the current release. | No Feature should be p0. | An issue that is not a bug and is blocking meaningful testing. eg. builds are failing because the syslog server is out of space. |
|
||||
| priority/p1 | Bugs that NEED to be fixed by the assigned phase of the current release. | A feature that is required for the next release, typically an anchor feature; a large feature that is the focus for the release and drives the release date. | An issue that must be fixed for the next release. eg. Track build success rates. |
|
||||
| priority/p2 | Bugs that SHOULD be fixed by the assigned phase of the current release, time permitting. | A feature that is desired for the next release, typically a pebble; a feature that has been approved for inclusion but is not considered the anchor feature or is considered good to have for the anchor feature. | An issue that we should fix in the next release. eg. A typo in the UI. |
|
||||
| priority/p3 | Bugs that SHOULD be fixed by a given release, time permitting. | A feature that can be fixed in the next release. eg. Migrate to a new kernel version. Or a feature that is nice to have for a pebble. | An issue that can be fixed in the next release. eg. Low hanging productivity improvements. |
|
||||
| priority/p4 | Bugs that SHOULD be fixed in a future (to be determined) release. | An issue or feature that will be fixed in a future release. | An issue or feature that will be fixed in a future release. |
|
||||
|
||||
### Not Ready
|
||||
|
||||
The Not Ready column is for issues that need more discussion, details and/or triaging before being put in the [Backlog](#backlog). Issues in Not Ready should have assignee(s) to track whose input is needed to put the issue in the Backlog. For issues reported by VIC engineers: if the issue's details aren't fleshed out, the reporter should set themselves as the assignee.
|
||||
|
||||
### Backlog
|
||||
|
||||
Issues in Backlog should be ready to be worked on in future sprints. For example, they may be feature requests or ideas for a future version of
|
||||
the project. When moving issues to the Backlog, add more information (like requirements and outlines) into each issue. It's useful to
|
||||
get ideas out of your head, even if you will not be touching them for a while.
|
||||
|
||||
To move an issue into the Backlog swim lane, it must have:
|
||||
|
||||
1. a `priority/...` label
|
||||
2. a `team/...` label
|
||||
3. an estimated level of effort (see [Story point estimates](#story-point-estimates) for guidance for mapping effort to story points)
|
||||
4. no assignee (assignees are set when the issue is selected to work on)
|
||||
|
||||
Other labels should be added as needed.
|
||||
|
||||
Prioritize issues by dragging and dropping their placement in the pipeline. Issues higher in the pipeline are higher
|
||||
priority; accordingly, they should contain all the information necessary to get started when the time
|
||||
comes. Low-priority issues should still contain at least a short description.
|
||||
|
||||
### To Do
|
||||
|
||||
This is the team's current focus and the issues should be well-defined. This pipeline should contain the high-priority
|
||||
items for the current milestone. These issues must have an assignee, milestone, estimate and tags. Items are moved
|
||||
from this pipeline to In Progress when work has been started.
|
||||
|
||||
To move an issue into the To Do swim lane, the assignee and milestone fields should be set.
|
||||
|
||||
### In Progress
|
||||
|
||||
This is the answer to, "What are you working on right now?" Ideally, this pipeline will not contain more issues than
|
||||
members of the team; each team member should be working on one thing at a time.
|
||||
|
||||
Issues in the In Progress swim lane must have an assignee.
|
||||
|
||||
After an issue is In Progress, it is best practice to update the issue with current progress and any discussions that may occur via the various collaboration tools used. An issue that is in progress should not go more than 2 days without updates.
|
||||
|
||||
Note: Epics should never be In Progress.
|
||||
|
||||
### Verify
|
||||
|
||||
A "Verify" issue normally means the feature or fix is in code review and/or awaiting further testing. These issues require one final QE sign off or at the end of a sprint another dev that didn't work on the issue can verify the issue.
|
||||
|
||||
In most cases, an issue should be in Verify _before_ the corresponding PR is merged. The developer can then close the issue while merging the PR.
|
||||
|
||||
### Closed
|
||||
|
||||
This pipeline includes all closed issues. It can be filtered like the rest of the Board – by Label, Assignee or Milestone.
|
||||
|
||||
This pipeline is also interactive: dragging issues into this pipeline will close them, while dragging them out will re-open them.
|
||||
|
||||
## Story point estimates
|
||||
|
||||
* Use the fibonacci pattern
|
||||
* All bugs are a 2 unless we know it is significantly more or less work than the average bug
|
||||
* 1 is easier than the average bug
|
||||
* 3 is slightly more work than the average bug and probably should be about an average feature work for an easy feature (which includes design doc, implementation, testing, review)
|
||||
* 5 is about 2x more work than the average bug and the highest single issue value we want
|
||||
* Issues with an estimate higher than 5 should be decomposed further
|
||||
* Unless otherwise necessary, estimates for EPICs are the sum of their sub-issues' estimates - EPICs aren't assigned an estimate themselves
|
||||
|
||||
## High level project planning
|
||||
|
||||
We use the following structure for higher level project management:
|
||||
* Epic (zenhub) - implements a functional change - for example 'attach, stdout only', may span milestones and releases. Expected to be broken down from larger Epics into smaller epics prior to commencement.
|
||||
* Milestones - essentially higher level user stories
|
||||
* Labels - either by functional area (`component/...`) or feature (`feature/...`)
|
||||
|
||||
## Repository structure
|
||||
|
||||
The layout in the repo is as follows - this is a recent reorganisation so there is still some mixing between directories:
|
||||
* cmd - the main packages for compiled components
|
||||
* doc - all project documentation other than the standard files in the root
|
||||
* infra - supporting scripts, utilities, et al
|
||||
* isos - ISO mastering scripts and uncompiled content
|
||||
* lib - common library packages that are tightly coupled to vmware/vic
|
||||
* pkg - packages that are not tightly coupled to vmware/vic and could be usefully consumed in other projects. There is still some sanitization to do here.
|
||||
* tests - integration and system test code that doesn't use go test
|
||||
* vendor - standard Go vendor model
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* If you're building the project in a VM, ensure that it has at least 4GB memory to avoid memory issues during a build.
|
||||
|
||||
8204
vendor/github.com/vmware/vic/LICENSE
generated
vendored
Normal file
8204
vendor/github.com/vmware/vic/LICENSE
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
550
vendor/github.com/vmware/vic/Makefile
generated
vendored
Normal file
550
vendor/github.com/vmware/vic/Makefile
generated
vendored
Normal file
@@ -0,0 +1,550 @@
|
||||
# Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
SHELL=/bin/bash
|
||||
|
||||
GO ?= go
|
||||
GOVERSION ?= go1.8
|
||||
OS := $(shell uname | tr '[:upper:]' '[:lower:]')
|
||||
ifeq (vagrant, $(filter vagrant,$(USER) $(SUDO_USER)))
|
||||
# assuming we are in a shared directory where host arch is different from the guest
|
||||
BIN_ARCH := -$(OS)
|
||||
endif
|
||||
REV :=$(shell git rev-parse --short=8 HEAD)
|
||||
TAG :=$(shell git for-each-ref --format="%(refname:short)" --sort=-authordate --count=1 refs/tags) # e.g. `v0.9.0`
|
||||
TAG_NUM :=$(shell git for-each-ref --format="%(refname:short)" --sort=-authordate --count=1 refs/tags | cut -c 2-) # e.g. `0.9.0`
|
||||
|
||||
BASE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
BASE_PKG := github.com/vmware/vic/
|
||||
|
||||
BIN ?= bin
|
||||
IGNORE := $(shell mkdir -p $(BIN))
|
||||
|
||||
export GOPATH ?= $(shell echo $(CURDIR) | sed -e 's,/src/.*,,')
|
||||
SWAGGER ?= $(GOPATH)/bin/swagger$(BIN_ARCH)
|
||||
GOIMPORTS ?= $(GOPATH)/bin/goimports$(BIN_ARCH)
|
||||
GOLINT ?= $(GOPATH)/bin/golint$(BIN_ARCH)
|
||||
GVT ?= $(GOPATH)/bin/gvt$(BIN_ARCH)
|
||||
GOVC ?= $(GOPATH)/bin/govc$(BIN_ARCH)
|
||||
GAS ?= $(GOPATH)/bin/gas$(BIN_ARCH)
|
||||
MISSPELL ?= $(GOPATH)/bin/misspell$(BIN_ARCH)
|
||||
|
||||
ifeq ($(VIRTUAL_KUBELET_PATH),)
|
||||
VIRTUAL_KUBELET := virtual-kubelet
|
||||
else
|
||||
VIRTUAL_KUBELET := $(VIRTUAL_KUBELET_PATH)
|
||||
endif
|
||||
|
||||
.PHONY: all tools clean test check distro \
|
||||
goversion goimports gopath govet gofmt misspell gas golint \
|
||||
isos tethers apiservers copyright
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
# allow deferred godeps calls
|
||||
.SECONDEXPANSION:
|
||||
|
||||
include infra/util/gsml/gmsl
|
||||
|
||||
ifeq ($(ENABLE_RACE_DETECTOR),true)
|
||||
RACE := -race
|
||||
else
|
||||
RACE :=
|
||||
endif
|
||||
|
||||
# Generate Go package dependency set, skipping if the only targets specified are clean and/or distclean
|
||||
# Caches dependencies to speed repeated calls
|
||||
define godeps
|
||||
$(call assert,$(call gmsl_compatible,1 1 7), Wrong GMSL version) \
|
||||
$(if $(filter-out push push-portlayer push-docker push-vic-init push-vicadmin focused-test test check clean distclean mrrobot mark sincemark local-ci-test .DEFAULT,$(MAKECMDGOALS)), \
|
||||
$(if $(call defined,dep_cache,$(dir $1)),,$(info Generating dependency set for $(dir $1))) \
|
||||
$(or \
|
||||
$(if $(call defined,dep_cache,$(dir $1)), $(debug Using cached Go dependencies) $(wildcard $1) $(call get,dep_cache,$(dir $1))),
|
||||
$(call set,dep_cache,$(dir $1),$(shell $(BASE_DIR)/infra/scripts/go-deps.sh $(dir $1) $(MAKEFLAGS))),
|
||||
$(debug Cached Go dependency for $(dir $1): $(call get,dep_cache,$(dir $1))),
|
||||
$(wildcard $1) $(call get,dep_cache,$(dir $1))
|
||||
) \
|
||||
)
|
||||
endef
|
||||
|
||||
ifeq ($(DISABLE_DEPENDENCY_TRACKING), true)
|
||||
define godeps
|
||||
endef
|
||||
endif
|
||||
|
||||
ifeq ($(VIC_DEBUG_BUILD),)
|
||||
LDFLAGS := $(shell BUILD_NUMBER=${BUILD_NUMBER} $(BASE_DIR)/infra/scripts/version-linker-flags.sh)
|
||||
else
|
||||
LDFLAGS := $(shell BUILD_NUMBER=${BUILD_NUMBER} $(BASE_DIR)/infra/scripts/version-linker-flags.sh debug)
|
||||
endif
|
||||
|
||||
# target aliases - environment variable definition
|
||||
docker-engine-api := $(BIN)/docker-engine-server
|
||||
docker-engine-api-test := $(BIN)/docker-engine-server-test
|
||||
admiralapi-client := lib/config/dynamic/admiral/client/admiral_client.go
|
||||
portlayerapi := $(BIN)/port-layer-server
|
||||
portlayerapi-test := $(BIN)/port-layer-server-test
|
||||
portlayerapi-client := lib/apiservers/portlayer/client/port_layer_client.go
|
||||
portlayerapi-server := lib/apiservers/portlayer/restapi/server.go
|
||||
serviceapi := $(BIN)/vic-machine-server
|
||||
serviceapi-server := lib/apiservers/service/restapi/server.go
|
||||
|
||||
imagec := $(BIN)/imagec
|
||||
vicadmin := $(BIN)/vicadmin
|
||||
rpctool := $(BIN)/rpctool
|
||||
vic-machine-linux := $(BIN)/vic-machine-linux
|
||||
vic-machine-windows := $(BIN)/vic-machine-windows.exe
|
||||
vic-machine-darwin := $(BIN)/vic-machine-darwin
|
||||
vic-ui-linux := $(BIN)/vic-ui-linux
|
||||
vic-ui-windows := $(BIN)/vic-ui-windows.exe
|
||||
vic-ui-darwin := $(BIN)/vic-ui-darwin
|
||||
vic-init := $(BIN)/vic-init
|
||||
vic-init-test := $(BIN)/vic-init-test
|
||||
kubelet-starter := $(BIN)/kubelet-starter
|
||||
# NOT BUILT WITH make all TARGET
|
||||
# vic-dns variants to create standalone DNS service.
|
||||
vic-dns-linux := $(BIN)/vic-dns-linux
|
||||
vic-dns-windows := $(BIN)/vic-dns-windows.exe
|
||||
vic-dns-darwin := $(BIN)/vic-dns-darwin
|
||||
archive := $(BIN)/unpack
|
||||
gandalf := $(BIN)/gandalf
|
||||
|
||||
tether-linux := $(BIN)/tether-linux
|
||||
|
||||
appliance := $(BIN)/appliance.iso
|
||||
appliance-vkubelet := $(BIN)/appliance-vkubelet.iso
|
||||
appliance-staging := $(BIN)/.appliance-staging.tgz
|
||||
bootstrap := $(BIN)/bootstrap.iso
|
||||
bootstrap-staging := $(BIN)/.bootstrap-staging.tgz
|
||||
bootstrap-staging-debug := $(BIN)/.bootstrap-staging-debug.tgz
|
||||
bootstrap-debug := $(BIN)/bootstrap-debug.iso
|
||||
iso-base := $(BIN)/.iso-base.tgz
|
||||
|
||||
# target aliases - target mapping
|
||||
docker-engine-api: $(docker-engine-api)
|
||||
docker-engine-api-test: $(docker-engine-api-test)
|
||||
portlayerapi: $(portlayerapi)
|
||||
portlayerapi-test: $(portlayerapi-test)
|
||||
portlayerapi-client: $(portlayerapi-client)
|
||||
portlayerapi-server: $(portlayerapi-server)
|
||||
serviceapi: $(serviceapi)
|
||||
serviceapi-server: $(serviceapi-server)
|
||||
admiralapi-client: $(admiralapi-client)
|
||||
|
||||
imagec: $(imagec)
|
||||
vicadmin: $(vicadmin)
|
||||
rpctool: $(rpctool)
|
||||
vic-init: $(vic-init)
|
||||
vic-init-test: $(vic-init-test)
|
||||
kubelet-starter: $(kubelet-starter)
|
||||
|
||||
tether-linux: $(tether-linux)
|
||||
|
||||
appliance: $(appliance)
|
||||
appliance-staging: $(appliance-staging)
|
||||
appliance-vkubelet: $(appliance-vkubelet)
|
||||
bootstrap: $(bootstrap)
|
||||
bootstrap-staging: $(bootstrap-staging)
|
||||
bootstrap-debug: $(bootstrap-debug)
|
||||
bootstrap-staging-debug: $(bootstrap-staging-debug)
|
||||
iso-base: $(iso-base)
|
||||
vic-machine: $(vic-machine-linux) $(vic-machine-windows) $(vic-machine-darwin)
|
||||
vic-ui: $(vic-ui-linux) $(vic-ui-windows) $(vic-ui-darwin)
|
||||
# NOT BUILT WITH make all TARGET
|
||||
# vic-dns variants to create standalone DNS service.
|
||||
vic-dns: $(vic-dns-linux) $(vic-dns-windows) $(vic-dns-darwin)
|
||||
gandalf: $(gandalf)
|
||||
|
||||
swagger: $(SWAGGER)
|
||||
goimports: $(GOIMPORTS)
|
||||
gas: $(GAS)
|
||||
misspell: $(MISSPELL)
|
||||
|
||||
# convenience targets
|
||||
all: components tethers isos vic-machine imagec vic-ui
|
||||
tools: $(GOIMPORTS) $(GVT) $(GOLINT) $(SWAGGER) $(GAS) $(MISSPELL) goversion
|
||||
check: goversion goimports gofmt misspell govet golint copyright whitespace gas
|
||||
apiservers: $(portlayerapi) $(docker-engine-api) $(serviceapi)
|
||||
components: check apiservers $(vicadmin) $(rpctool)
|
||||
isos: $(appliance) $(bootstrap)
|
||||
tethers: $(tether-linux)
|
||||
|
||||
most: $(portlayerapi) $(docker-engine-api) $(vicadmin) $(tether-linux) $(appliance) $(bootstrap) $(vic-machine-linux) $(serviceapi)
|
||||
|
||||
most-vkubelet: $(portlayerapi) $(docker-engine-api) $(vicadmin) $(tether-linux) $(appliance-vkubelet) $(bootstrap) $(vic-machine-linux) $(serviceapi)
|
||||
|
||||
# utility targets
|
||||
goversion:
|
||||
@echo checking go version...
|
||||
@( $(GO) version | grep -q $(GOVERSION) ) || ( echo "Please install $(GOVERSION) (found: $$($(GO) version))" && exit 1 )
|
||||
|
||||
$(GOIMPORTS): vendor/manifest
|
||||
@echo building $(GOIMPORTS)...
|
||||
@$(GO) build $(RACE) -o $(GOIMPORTS) ./vendor/golang.org/x/tools/cmd/goimports
|
||||
|
||||
$(GVT): vendor/manifest
|
||||
@echo building $(GVT)...
|
||||
@$(GO) build $(RACE) -o $(GVT) ./vendor/github.com/FiloSottile/gvt
|
||||
|
||||
$(GOLINT): vendor/manifest
|
||||
@echo building $(GOLINT)...
|
||||
@$(GO) build $(RACE) -o $(GOLINT) ./vendor/github.com/golang/lint/golint
|
||||
|
||||
$(SWAGGER): vendor/manifest
|
||||
@echo building $(SWAGGER)...
|
||||
@$(GO) build $(RACE) -o $(SWAGGER) ./vendor/github.com/go-swagger/go-swagger/cmd/swagger
|
||||
|
||||
$(GOVC): vendor/manifest
|
||||
@echo building $(GOVC)...
|
||||
@$(GO) build $(RACE) -o $(GOVC) ./vendor/github.com/vmware/govmomi/govc
|
||||
|
||||
$(GAS): vendor/manifest
|
||||
@echo building $(GAS)...
|
||||
@$(GO) build $(RACE) -o $(GAS) ./vendor/github.com/GoASTScanner/gas
|
||||
|
||||
$(MISSPELL): vendor/manifest
|
||||
@echo building $(MISSPELL)...
|
||||
@$(GO) build $(RACE) -o $(MISSPELL) ./vendor/github.com/client9/misspell/cmd/misspell
|
||||
|
||||
copyright:
|
||||
@echo "checking copyright in header..."
|
||||
@infra/scripts/header-check.sh
|
||||
|
||||
whitespace:
|
||||
@echo "checking whitespace..."
|
||||
@infra/scripts/whitespace-check.sh
|
||||
|
||||
# exit 1 if golint complains about anything other than comments
|
||||
golintf = $(GOLINT) $(1) | sh -c "! grep -v 'lib/apiservers/portlayer/restapi/operations'" | sh -c "! grep -v 'lib/config/dynamic/admiral/client'" | sh -c "! grep -v 'should have comment'" | sh -c "! grep -v 'comment on exported'" | sh -c "! grep -v 'by other packages, and that stutters'" | sh -c "! grep -v 'error strings should not be capitalized'"
|
||||
|
||||
golint: $(GOLINT)
|
||||
@echo checking go lint...
|
||||
@$(call golintf,github.com/vmware/vic/cmd/...)
|
||||
@$(call golintf,github.com/vmware/vic/pkg/...)
|
||||
@$(call golintf,github.com/vmware/vic/lib/...)
|
||||
|
||||
# For use by external tools such as emacs or for example:
|
||||
# GOPATH=$(make gopath) go get ...
|
||||
gopath:
|
||||
@echo -n $(GOPATH)
|
||||
|
||||
goimports: $(GOIMPORTS)
|
||||
@echo checking go imports...
|
||||
@! if test -e swagger-gen.log; then $(GOIMPORTS) -local github.com/vmware -d $$(\
|
||||
comm -23 <(find . -type f -name '*.go' -not -path "./vendor/*" | sort) \
|
||||
<(grep creating swagger-gen.log | awk '{print $$6 $$4};' | sed -e "s-\"\(.*\)\"-\./\1-g" | sed "s-\"\"-/-g" | sort)) \
|
||||
| egrep -v '^$$'; else $(GOIMPORTS) -local github.com/vmware -d $$(find . -type f -name '*.go' -not -path "./vendor/*") | egrep -v "^$$"; fi
|
||||
|
||||
gofmt:
|
||||
@echo checking gofmt...
|
||||
@! gofmt -d -e -s $$(find . -mindepth 1 -maxdepth 1 -type d -not -name vendor) 2>&1 | egrep -v '^$$'
|
||||
|
||||
misspell: $(MISSPELL)
|
||||
@echo checking misspell...
|
||||
@infra/scripts/misspell.sh
|
||||
|
||||
govet:
|
||||
@echo checking go vet...
|
||||
@$(GO) tool vet -all -lostcancel -tests $$(find . -mindepth 1 -maxdepth 1 -type d -not -name vendor)
|
||||
# one day we will enable shadow check
|
||||
# @$(GO) tool vet -all -shadow -lostcancel -tests $$(find . -mindepth 1 -maxdepth 1 -type d -not -name vendor)
|
||||
|
||||
gas: $(GAS)
|
||||
@echo checking security problems
|
||||
@$(GAS) -skip=*_responses.go -quiet lib/... cmd/... pkg/... 2> /dev/null
|
||||
|
||||
vendor: $(GVT)
|
||||
@echo restoring vendor
|
||||
$(GVT) restore
|
||||
|
||||
TEST_DIRS=github.com/vmware/vic/cmd
|
||||
TEST_DIRS+=github.com/vmware/vic/lib
|
||||
TEST_DIRS+=github.com/vmware/vic/pkg
|
||||
|
||||
TEST_JOBS := $(addprefix test-job-,$(TEST_DIRS))
|
||||
|
||||
# since drone cannot tell us how log it took
|
||||
mark:
|
||||
@echo touching /started to mark beginning of the time
|
||||
@touch /started
|
||||
sincemark:
|
||||
@echo seconds passed since we start
|
||||
@stat -c %Y /started | echo `expr $$(date +%s) - $$(cat)`
|
||||
|
||||
install-govmomi:
|
||||
# manually install govmomi so the huge types package doesn't break cover
|
||||
$(GO) install ./vendor/github.com/vmware/govmomi
|
||||
|
||||
test: install-govmomi portlayerapi $(TEST_JOBS)
|
||||
|
||||
push:
|
||||
$(BASE_DIR)/infra/scripts/replace-running-components.sh
|
||||
|
||||
push-portlayer:
|
||||
$(BASE_DIR)/infra/scripts/replace-running-components.sh port-layer-server
|
||||
|
||||
push-docker:
|
||||
$(BASE_DIR)/infra/scripts/replace-running-components.sh docker-engine-server
|
||||
|
||||
push-vic-init:
|
||||
$(BASE_DIR)/infra/scripts/replace-running-components.sh vic-init
|
||||
|
||||
push-vicadmin:
|
||||
$(BASE_DIR)/infra/scripts/replace-running-components.sh vicadmin
|
||||
|
||||
local-ci-test:
|
||||
@echo running CI tests locally...
|
||||
infra/scripts/local-ci.sh
|
||||
|
||||
focused-test:
|
||||
# test only those packages that have changes
|
||||
infra/scripts/focused-test.sh $(REMOTE)
|
||||
|
||||
$(TEST_JOBS): test-job-%:
|
||||
@echo Running unit tests
|
||||
# test everything but vendor
|
||||
ifdef DRONE
|
||||
@echo Generating coverage data
|
||||
@$(TIME) infra/scripts/coverage.sh $*
|
||||
else
|
||||
@echo Generating local html coverage report
|
||||
@$(TIME) infra/scripts/coverage.sh --html $*
|
||||
endif
|
||||
|
||||
$(vic-init): $$(call godeps,cmd/vic-init/*.go)
|
||||
@echo building vic-init
|
||||
@CGO_ENABLED=1 GOOS=linux GOARCH=amd64 $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -tags netgo -installsuffix netgo -o ./$@ ./$(dir $<)
|
||||
|
||||
$(vic-init-test): $$(call godeps,cmd/vic-init/*.go)
|
||||
@echo building vic-init-test
|
||||
@CGO_ENABLED=1 GOOS=linux GOARCH=amd64 $(GO) test -c -coverpkg github.com/vmware/vic/lib/...,github.com/vmware/vic/pkg/... -outputdir /tmp -coverprofile init.cov -o ./$@ ./$(dir $<)
|
||||
|
||||
$(kubelet-starter): $$(call godeps,cmd/kubelet-starter/*.go)
|
||||
@echo building kubelet-starter
|
||||
@CGO_ENABLED=1 GOOS=linux GOARCH=amd64 $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -tags netgo -installsuffix netgo -o ./$@ ./$(dir $<)
|
||||
|
||||
$(tether-linux): $$(call godeps,cmd/tether/*.go)
|
||||
@echo building tether-linux
|
||||
@CGO_ENABLED=1 GOOS=linux GOARCH=amd64 $(TIME) $(GO) build $(RACE) -tags netgo -installsuffix netgo -ldflags '$(LDFLAGS) -extldflags "-static"' -o ./$@ ./$(dir $<)
|
||||
|
||||
$(rpctool): $$(call godeps,cmd/rpctool/*.go)
|
||||
ifeq ($(OS),linux)
|
||||
@echo building rpctool
|
||||
@GOARCH=amd64 GOOS=linux $(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o ./$@ ./$(dir $<)
|
||||
else
|
||||
@echo skipping rpctool, cannot cross compile cgo
|
||||
endif
|
||||
|
||||
$(vicadmin): $$(call godeps,cmd/vicadmin/*.go)
|
||||
@echo building vicadmin
|
||||
@GOARCH=amd64 GOOS=linux $(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o ./$@ ./$(dir $<)
|
||||
|
||||
$(archive): $$(call godeps,cmd/archive/*.go)
|
||||
@echo building archive
|
||||
@GOARCH=amd64 GOOS=linux $(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o ./$@ ./$(dir $<)
|
||||
|
||||
$(imagec): $(call godeps,cmd/imagec/*.go) $(portlayerapi-client)
|
||||
@echo building imagec...
|
||||
@$(TIME) $(GO) build $(RACE) $(ldflags) -o ./$@ ./$(dir $<)
|
||||
|
||||
$(docker-engine-api): $(portlayerapi-client) $(admiralapi-client) $$(call godeps,cmd/docker/*.go)
|
||||
ifeq ($(OS),linux)
|
||||
@echo building docker-engine-api server...
|
||||
@$(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o $@ ./cmd/docker
|
||||
else
|
||||
@echo skipping docker-engine-api server, cannot build on non-linux
|
||||
endif
|
||||
|
||||
$(docker-engine-api-test): $$(call godeps,cmd/docker/*.go) $(portlayerapi-client)
|
||||
ifeq ($(OS),linux)
|
||||
@echo building docker-engine-api server for test...
|
||||
@$(TIME) $(GO) test -c -coverpkg github.com/vmware/vic/lib/...,github.com/vmware/vic/pkg/... -outputdir /tmp -coverprofile docker-engine-api.cov -o $@ ./cmd/docker
|
||||
else
|
||||
@echo skipping docker-engine-api server for test, cannot build on non-linux
|
||||
endif
|
||||
|
||||
|
||||
# Common portlayer dependencies between client and server
|
||||
PORTLAYER_DEPS ?= lib/apiservers/portlayer/swagger.json \
|
||||
lib/apiservers/portlayer/restapi/configure_port_layer.go \
|
||||
lib/apiservers/portlayer/restapi/options/*.go
|
||||
|
||||
$(admiralapi-client): lib/config/dynamic/admiral/swagger.json $(SWAGGER)
|
||||
@echo regenerating swagger models and operations for Admiral API client...
|
||||
@$(SWAGGER) generate client -A Admiral --target lib/config/dynamic/admiral \
|
||||
-f lib/config/dynamic/admiral/swagger.json \
|
||||
--tags /projects \
|
||||
--tags /resources/compute \
|
||||
--tags /config/registries \
|
||||
-O GetResourcesCompute \
|
||||
-O GetProjects \
|
||||
-O GetConfigRegistriesID \
|
||||
-M "com:vmware:photon:controller:model:resources:ComputeService:ComputeState" \
|
||||
-M "com:vmware:xenon:common:ServiceDocumentQueryResult" \
|
||||
-M "com:vmware:admiral:service:common:RegistryService:RegistryState" \
|
||||
-M "com:vmware:xenon:common:ServiceDocumentQueryResult:ContinuousResult" \
|
||||
-M "com:vmware:xenon:common:ServiceErrorResponse" \
|
||||
2>>swagger-gen.log
|
||||
@echo done regenerating swagger models and operations for Admiral API client...
|
||||
|
||||
$(portlayerapi-client): $(PORTLAYER_DEPS) $(SWAGGER)
|
||||
@echo regenerating swagger models and operations for Portlayer API client...
|
||||
@$(SWAGGER) generate client -A PortLayer --target lib/apiservers/portlayer -f lib/apiservers/portlayer/swagger.json 2>>swagger-gen.log
|
||||
@echo done regenerating swagger models and operations for Portlayer API client...
|
||||
|
||||
$(portlayerapi-server): $(PORTLAYER_DEPS) $(SWAGGER)
|
||||
@echo regenerating swagger models and operations for Portlayer API server...
|
||||
@$(SWAGGER) generate server --exclude-main -A PortLayer --target lib/apiservers/portlayer -f lib/apiservers/portlayer/swagger.json 2>>swagger-gen.log
|
||||
@echo done regenerating swagger models and operations for Portlayer API server...
|
||||
|
||||
$(portlayerapi): $(portlayerapi-server) $(portlayerapi-client) $$(call godeps,cmd/port-layer-server/*.go)
|
||||
@echo building Portlayer API server...
|
||||
@$(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o $@ ./cmd/port-layer-server
|
||||
|
||||
$(portlayerapi-test): $$(call godeps,cmd/port-layer-server/*.go) $(portlayerapi-server) $(portlayerapi-client)
|
||||
@echo building Portlayer API server for test...
|
||||
@$(TIME) $(GO) test -c -coverpkg github.com/vmware/vic/lib/...,github.com/vmware/vic/pkg/... -coverprofile port-layer-server.cov -outputdir /tmp -o $@ ./cmd/port-layer-server
|
||||
|
||||
# Common service dependencies between client and server
|
||||
SERVICE_DEPS ?= lib/apiservers/service/swagger.json \
|
||||
lib/apiservers/service/restapi/configure_vic_machine.go \
|
||||
lib/apiservers/service/restapi/handlers/*.go
|
||||
|
||||
$(serviceapi-server): $(SERVICE_DEPS) $(SWAGGER)
|
||||
@echo regenerating swagger models and operations for vic-machine-as-a-service API server...
|
||||
@$(SWAGGER) generate server --exclude-main --target lib/apiservers/service -f lib/apiservers/service/swagger.json 2>>service-swagger-gen.log
|
||||
@echo done regenerating swagger models and operations for vic-machine-as-a-service API server...
|
||||
|
||||
$(serviceapi): $$(call godeps,cmd/vic-machine-server/*.go) $(serviceapi-server)
|
||||
@echo building vic-machine-as-a-service API server...
|
||||
@$(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o $@ ./cmd/vic-machine-server
|
||||
|
||||
|
||||
$(iso-base): isos/base.sh isos/base/*.repo isos/base/isolinux/** isos/base/xorriso-options.cfg
|
||||
@echo building iso-base docker image
|
||||
@$(TIME) $< -c $(BIN)/.yum-cache.tgz -p $@
|
||||
|
||||
# appliance staging - allows for caching of package install
|
||||
$(appliance-staging): isos/appliance-staging.sh $(iso-base)
|
||||
@echo staging for VCH appliance
|
||||
@$(TIME) $< -c $(BIN)/.yum-cache.tgz -p $(iso-base) -o $@
|
||||
|
||||
# main appliance target - depends on all top level component targets
|
||||
$(appliance): isos/appliance.sh isos/appliance/* isos/vicadmin/** $(vicadmin) $(vic-init) $(portlayerapi) $(docker-engine-api) $(appliance-staging) $(archive)
|
||||
@echo building VCH appliance ISO
|
||||
@$(TIME) $< -p $(appliance-staging) -b $(BIN)
|
||||
|
||||
.PHONY: $(appliance-vkubelet)
|
||||
# main appliance target + virtual kubelet - depends on all top level component targets
|
||||
$(appliance-vkubelet): isos/appliance-virtual-kubelet.sh isos/appliance/* isos/vicadmin/** $(vicadmin) $(vic-init) $(kubelet-starter) $(portlayerapi) $(docker-engine-api) $(appliance-staging) $(archive)
|
||||
@echo building VCH appliance ISO
|
||||
@$(TIME) $< -p $(appliance-staging) -b $(BIN) -x $(VIRTUAL_KUBELET) -f virtual-kubelet -o $@
|
||||
|
||||
# main bootstrap target
|
||||
$(bootstrap): isos/bootstrap.sh $(tether-linux) $(bootstrap-staging) isos/bootstrap/*
|
||||
@echo "Making bootstrap iso"
|
||||
@$(TIME) $< -p $(bootstrap-staging) -b $(BIN)
|
||||
|
||||
$(bootstrap-debug): isos/bootstrap.sh $(tether-linux) $(rpctool) $(bootstrap-staging-debug) isos/bootstrap/*
|
||||
@echo "Making bootstrap-debug iso"
|
||||
@$(TIME) $< -p $(bootstrap-staging-debug) -b $(BIN) -d true
|
||||
|
||||
$(bootstrap-staging): isos/bootstrap-staging.sh $(iso-base)
|
||||
@echo staging for bootstrap
|
||||
@$(TIME) $< -c $(BIN)/.yum-cache.tgz -p $(iso-base) -o $@
|
||||
|
||||
$(bootstrap-staging-debug): isos/bootstrap-staging.sh $(iso-base)
|
||||
@echo staging debug for bootstrap
|
||||
@$(TIME) $< -c $(BIN)/.yum-cache.tgz -p $(iso-base) -o $@ -d true
|
||||
|
||||
$(vic-machine-linux): $$(call godeps,cmd/vic-machine/*.go)
|
||||
@echo building vic-machine linux...
|
||||
@GOARCH=amd64 GOOS=linux $(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o ./$@ ./$(dir $<)
|
||||
|
||||
$(vic-machine-windows): $$(call godeps,cmd/vic-machine/*.go)
|
||||
@echo building vic-machine windows...
|
||||
@GOARCH=amd64 GOOS=windows $(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o ./$@ ./$(dir $<)
|
||||
|
||||
$(vic-machine-darwin): $$(call godeps,cmd/vic-machine/*.go)
|
||||
@echo building vic-machine darwin...
|
||||
@GOARCH=amd64 GOOS=darwin $(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o ./$@ ./$(dir $<)
|
||||
|
||||
$(vic-ui-linux): $$(call godeps,cmd/vic-ui/*.go) $(admiralapi-client)
|
||||
@echo building vic-ui linux...
|
||||
@GOARCH=amd64 GOOS=linux $(TIME) $(GO) build $(RACE) -ldflags "-X main.BuildID=${BUILD_NUMBER} -X main.CommitID=${COMMIT}" -o ./$@ ./$(dir $<)
|
||||
|
||||
$(vic-ui-windows): $$(call godeps,cmd/vic-ui/*.go) $(admiralapi-client)
|
||||
@echo building vic-ui windows...
|
||||
@GOARCH=amd64 GOOS=windows $(TIME) $(GO) build $(RACE) -ldflags "-X main.BuildID=${BUILD_NUMBER} -X main.CommitID=${COMMIT}" -o ./$@ ./$(dir $<)
|
||||
|
||||
$(vic-ui-darwin): $$(call godeps,cmd/vic-ui/*.go) $(admiralapi-client)
|
||||
@echo building vic-ui darwin...
|
||||
@GOARCH=amd64 GOOS=darwin $(TIME) $(GO) build $(RACE) -ldflags "-X main.BuildID=${BUILD_NUMBER} -X main.CommitID=${COMMIT}" -o ./$@ ./$(dir $<)
|
||||
|
||||
$(vic-dns-linux): $$(call godeps,cmd/vic-dns/*.go)
|
||||
@echo building vic-dns linux...
|
||||
@GOARCH=amd64 GOOS=linux $(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o ./$@ ./$(dir $<)
|
||||
|
||||
$(vic-dns-windows): $$(call godeps,cmd/vic-dns/*.go)
|
||||
@echo building vic-dns windows...
|
||||
@GOARCH=amd64 GOOS=windows $(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o ./$@ ./$(dir $<)
|
||||
|
||||
$(vic-dns-darwin): $$(call godeps,cmd/vic-dns/*.go)
|
||||
@echo building vic-dns darwin...
|
||||
@GOARCH=amd64 GOOS=darwin $(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o ./$@ ./$(dir $<)
|
||||
|
||||
$(gandalf): $$(call godeps,cmd/gandalf/*.go)
|
||||
@echo building gandalf...
|
||||
@GOARCH=amd64 GOOS=linux $(TIME) $(GO) build $(RACE) -ldflags "$(LDFLAGS)" -o ./$@ ./$(dir $<)
|
||||
|
||||
distro: all
|
||||
@tar czvf $(REV).tar.gz bin/*.iso bin/vic-machine-* --exclude=bin/vic-machine-server
|
||||
|
||||
mrrobot:
|
||||
@rm -rf *.xml *.html *.log *.zip VCH-0-*
|
||||
|
||||
clean: cleandeps
|
||||
@echo removing binaries
|
||||
@rm -rf $(BIN)/*
|
||||
@echo removing Go object files
|
||||
@$(GO) clean
|
||||
|
||||
@echo removing swagger generated files...
|
||||
@rm -f ./lib/apiservers/portlayer/restapi/doc.go
|
||||
@rm -f ./lib/apiservers/portlayer/restapi/embedded_spec.go
|
||||
@rm -f ./lib/apiservers/portlayer/restapi/server.go
|
||||
@rm -rf ./lib/apiservers/portlayer/cmd/
|
||||
@rm -rf ./lib/apiservers/portlayer/restapi/operations/
|
||||
@rm -rf ./lib/config/dynamic/admiral/client
|
||||
@rm -rf ./lib/config/dynamic/admiral/models
|
||||
@rm -rf ./lib/config/dynamic/admiral/operations
|
||||
|
||||
@rm -f ./lib/apiservers/service/restapi/doc.go
|
||||
@rm -f ./lib/apiservers/service/restapi/embedded_spec.go
|
||||
@rm -f ./lib/apiservers/service/restapi/server.go
|
||||
@rm -rf ./lib/apiservers/service/restapi/cmd/
|
||||
@rm -rf ./lib/apiservers/service/restapi/models/
|
||||
@rm -rf ./lib/apiservers/service/restapi/operations/
|
||||
|
||||
@rm -f *.log
|
||||
@rm -f *.pem
|
||||
|
||||
# removes the yum cache as well as the generated binaries
|
||||
distclean: clean
|
||||
@echo removing binaries
|
||||
@rm -rf $(BIN)
|
||||
|
||||
cleandeps:
|
||||
@echo removing dependency cache
|
||||
@rm -rf .godeps_cache
|
||||
190
vendor/github.com/vmware/vic/README.md
generated
vendored
Normal file
190
vendor/github.com/vmware/vic/README.md
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
[](https://ci-vic.vmware.com/vmware/vic) [](https://codecov.io/gh/vmware/vic) [](https://github.com/vmware/vic/releases/latest) [](https://goreportcard.com/report/github.com/vmware/vic)
|
||||
|
||||
# vSphere Integrated Containers Engine
|
||||
|
||||
vSphere Integrated Containers Engine (VIC Engine) is a container runtime for vSphere, allowing developers familiar with Docker to develop in containers and deploy them alongside traditional VM-based workloads on vSphere clusters, and allowing for these workloads to be managed through the vSphere UI in a way familiar to existing vSphere admins.
|
||||
|
||||
See [VIC Engine Architecture](doc/design/arch/arch.md) for a high level overview.
|
||||
|
||||
## Project Status
|
||||
|
||||
[commands]:https://vmware.github.io/vic-product/assets/files/html/1.3/vic_app_dev/container_operations.html
|
||||
[ctrust]:https://docs.docker.com/engine/security/trust/content_trust
|
||||
[wizard]:https://vmware.github.io/vic-product/assets/files/html/1.3/vic_vsphere_admin/deploy_vch_client.html
|
||||
[dch]:https://vmware.github.io/vic-product/assets/files/html/1.3/vic_vsphere_admin/deploy_vch_dchphoton.html
|
||||
|
||||
VIC Engine now provides:
|
||||
* support for most of the Docker commands for core container, image, volume and network lifecycle operations. Several `docker compose` commands are also supported. See the complete list of supported commands [here][commands].
|
||||
* vCenter support, leveraging DRS for initial placement. vMotion is also supported.
|
||||
* volume support for standard datastores such as vSAN and iSCSI datastores. NFS shares are also supported. See [--volume-store](doc/user/usage.md#configuring-volumes-in-a-virtual-container-host) - SIOC is not integrated but can be set as normal.
|
||||
* direct mapping of vSphere networks [--container-network](doc/user/usage.md#exposing-vsphere-networks-within-a-virtual-container-host) - NIOC is not integrated but can be set as normal.
|
||||
* dual-mode management - IP addresses are reported as normal via vSphere UI, guest shutdown via the UI will trigger delivery of container STOPSIGNAL, restart will relaunch container process.
|
||||
* client authentication - basic authentication via client certificates known as _tlsverify_.
|
||||
* integration with the VIC Management Portal (Admiral) for Docker image [content trust][ctrust].
|
||||
* integration with the vSphere Platform Services Controller (PSC) for Single Sign-on (SSO) for docker commands such as `docker login`.
|
||||
* an install wizard in the vSphere HTML5 client, as a more interactive alternative to installing via the command line. See details [here][wizard].
|
||||
* support for a standard Docker Container Host (DCH) deployed and managed as a container on VIC Engine. This can be used to run docker commands that are not currently supported by VIC Engine (`docker build, docker push`). See details [here][dch].
|
||||
|
||||
We are working hard to add functionality while building out our [foundation](doc/design/arch/arch.md#port-layer-abstractions) so continue to watch the repo for new features. Initial focus is on the production end of the CI pipeline, building backwards towards developer laptop scenarios.
|
||||
|
||||
## Installing
|
||||
|
||||
After building the binaries (see the [Building](#building) section), pick up the correct binary based on your OS, and install the Virtual Container Host (VCH) with the following command. For Linux:
|
||||
|
||||
```shell
|
||||
bin/vic-machine-linux create --target <target-host>[/datacenter] --image-store <datastore name> --name <vch-name> --user <username> --password <password> --thumbprint <certificate thumbprint> --compute-resource <cluster or resource pool name> --tls-cname <FQDN, *.wildcard.domain, or static IP>
|
||||
```
|
||||
|
||||
See `vic-machine-$OS create --help` for usage information. A more in-depth example can be found [here](doc/user/usage.md#deploying-a-virtual-container-host).
|
||||
|
||||
## Deleting
|
||||
|
||||
The installed VCH can be deleted using `vic-machine-$OS delete`.
|
||||
|
||||
See `vic-machine-$OS delete --help` for usage information. A more in-depth example can be found [here](doc/user/usage.md#deleting-a-virtual-container-host).
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING](CONTRIBUTING.md) for details on submitting changes and the contribution workflow.
|
||||
|
||||
## Building
|
||||
|
||||
Building the project is done with a combination of make and containers, with golang:1.8 being the common container base. This is done so that it's possible to build directly, without a functional docker, if using a Debian based system with the Go 1.8 toolchain and Drone.io installed.
|
||||
|
||||
To build as closely as possible to the formal build:
|
||||
```shell
|
||||
drone exec
|
||||
```
|
||||
|
||||
To build inside a Docker container:
|
||||
```shell
|
||||
docker run -v $(pwd):/go/src/github.com/vmware/vic -w /go/src/github.com/vmware/vic golang:1.8 make all
|
||||
```
|
||||
|
||||
To build directly:
|
||||
```shell
|
||||
make all
|
||||
```
|
||||
|
||||
There are three primary components generated by a full build, found in `$BIN` (the ./bin directory by default). The make targets used are the following:
|
||||
1. vic-machine - `make vic-machine`
|
||||
2. appliance.iso - `make appliance`
|
||||
3. bootstrap.iso - `make bootstrap`
|
||||
|
||||
## Building binaries for development
|
||||
|
||||
Some of the project binaries can only be built on Linux. If you are developing on a Mac or Windows OS, then the easiest way to facilitate a build is by utilizing the project's Vagrantfile. The Vagrantfile will share the directory where the file is executed and set the GOPATH based on that share.
|
||||
|
||||
To build the component binaries, ensure `GOPATH` is set, then issue the following command in the root directory:
|
||||
```shell
|
||||
make components
|
||||
```
|
||||
This will install required tools and build the component binaries `tether-linux`, `rpctool` and server binaries `docker-engine-server`, `port-layer-server`. The binaries will be created in the `$BIN` directory, ./bin by default.
|
||||
|
||||
To run unit tests after a successful build, issue the following:
|
||||
```shell
|
||||
make test
|
||||
```
|
||||
|
||||
Running "make" every time causes Go dependency regeneration for each component, so that "make" can rebuild only those components that are changed. However, such regeneration may take significant amount of time when it is not really needed. To fight that developers can use cached dependencies that can be enabled by defining the environment variable VIC_CACHE_DEPS. As soon as it is set, infra/scripts/go-deps.sh will read cached version of dependencies if those exist.
|
||||
|
||||
```shell
|
||||
export VIC_CACHE_DEPS=1
|
||||
```
|
||||
|
||||
This is important to note that as soon as you add a new package or an internal project dependency that didn't exist before, those dependencies
|
||||
should be regenerated to reflect latest changes. It can be done just by running:
|
||||
|
||||
```shell
|
||||
make cleandeps
|
||||
```
|
||||
|
||||
After that next "make" run will regenerate dependencies from scratch.
|
||||
|
||||
|
||||
## Managing vendor/ directory
|
||||
|
||||
To build the VIC Engine dependencies, ensure `GOPATH` is set, then issue the following.
|
||||
```shell
|
||||
make gvt vendor
|
||||
```
|
||||
|
||||
This will install the [gvt](https://github.com/FiloSottile/gvt) utility and retrieve the build dependencies via `gvt restore`.
|
||||
|
||||
## Building the ISOs
|
||||
|
||||
The component binaries above are packaged into ISO files, appliance.iso and bootstrap.iso, that are used by the installer. The generation of the ISOs is split into the following targets:
|
||||
iso-base, appliance-staging, bootstrap-staging, appliance, and bootstrap. Generation of the ISOs involves authoring a new root filesystem, meaning running a package manager (currently yum) and packing/unpacking archives. To install packages and preserve file permissions while unpacking these steps should be run as root, whether directly or in a container. To generate the ISOs:
|
||||
|
||||
```shell
|
||||
make isos
|
||||
```
|
||||
|
||||
The appliance and bootstrap ISOs are bootable CD images used to start the VMs that make up VIC Engine. To build the image using [docker](https://www.docker.com/), ensure `GOPATH` is set and `docker` is installed, then issue the following.
|
||||
|
||||
```shell
|
||||
docker run -v $(pwd):/go/src/github.com/vmware/vic -w /go/src/github.com/vmware/vic golang:1.8 make isos
|
||||
```
|
||||
|
||||
Alternatively, the iso image can be built locally. Again, ensure `GOPATH` is set, but also ensure the following packages are installed. This will attempt to install the following packages if not present using apt-get:
|
||||
|
||||
```shell
|
||||
apt-get install \
|
||||
curl \
|
||||
cpio \
|
||||
tar \
|
||||
xorriso \
|
||||
rpm \
|
||||
ca-certificates \
|
||||
yum
|
||||
```
|
||||
|
||||
Package names may vary depending on the distribution being used. Once installed, issue the following (the targets listed here are those executed when using the `iso` target.
|
||||
|
||||
```shell
|
||||
make iso-base appliance-staging appliance bootstrap-staging bootstrap
|
||||
```
|
||||
|
||||
The iso image will be created in `$BIN`
|
||||
|
||||
|
||||
## Building with CI
|
||||
|
||||
[dronevic]:https://ci-vic.vmware.com/vmware/vic
|
||||
[dronesrc]:https://github.com/drone/drone
|
||||
[dronecli]:http://docs.drone.io/cli-installation/
|
||||
|
||||
PRs to this repository will trigger builds on our [Drone CI][dronevic].
|
||||
|
||||
To build locally with Drone:
|
||||
|
||||
Ensure that you have Docker 1.6 or higher installed.
|
||||
Install the [Drone command line tools][dronecli].
|
||||
From the root directory of the `vic` repository run `drone exec`
|
||||
|
||||
## Common Build Problems
|
||||
1. Builds may fail when building either the appliance.iso or bootstrap.iso with the error: `cap_set_file failed - Operation not supported`
|
||||
|
||||
*Cause:* Some Ubuntu and Debian based systems ship with a defective `aufs` driver, which Docker uses as its default backing store. This driver does not support extended file capabilities such as `cap_set_file`
|
||||
|
||||
*Solution:* Edit the `/etc/default/docker` file, add the option `--storage-driver=overlay` to the `DOCKER_OPTS` settings, and restart Docker.
|
||||
|
||||
2. `go vet` fails when doing a `make all`
|
||||
|
||||
*Cause:* Apparently some caching takes place in `$GOPATH/pkg/linux_amd64/github.com/vmware/vic` and can cause `go vet` to fail when evaluating outdated files in this cache.
|
||||
|
||||
*Solution:* Delete everything under `$GOPATH/pkg/linux_amd64/github.com/vmware/vic` and re-run `make all`.
|
||||
|
||||
3. `vic-machine upgrade` integration tests fail due to `BUILD_NUMBER` being set incorrectly when building locally
|
||||
|
||||
*Cause:* `vic-machine` checks the build number of its binary to determine upgrade status and a locally-built `vic-machine` binary may not have the `BUILD_NUMBER` set correctly. Upon running `vic-machine upgrade`, it may fail with the message `foo-VCH has same or newer version x than installer version y. No upgrade is available.`
|
||||
|
||||
*Solution:* Set `BUILD_NUMBER` to a high number at the top of the `Makefile` - `BUILD_NUMBER ?= 9999999999`. Then, re-build binaries - `sudo make distclean && sudo make clean && sudo make all` and run `vic-machine upgrade` with the new binary.
|
||||
|
||||
## Integration Tests
|
||||
|
||||
[VIC Engine Integration Test Suite](tests/README.md) includes instructions to run locally.
|
||||
|
||||
## License
|
||||
|
||||
VIC Engine is available under the [Apache 2 license](LICENSE).
|
||||
37
vendor/github.com/vmware/vic/Vagrantfile
generated
vendored
Normal file
37
vendor/github.com/vmware/vic/Vagrantfile
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- mode: ruby -*-
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
dirs = ENV['GOPATH'] || Dir.home
|
||||
gdir = nil
|
||||
config.ssh.forward_agent = true
|
||||
config.vm.define "vic_dev" do | vic_dev |
|
||||
vic_dev.vm.box = 'bento/ubuntu-16.04'
|
||||
vic_dev.vm.network 'forwarded_port', guest: 2375, host: 12375, auto_correct: true
|
||||
vic_dev.vm.host_name = 'devbox'
|
||||
vic_dev.vm.synced_folder '.', '/vagrant', disabled: true
|
||||
vic_dev.ssh.username = 'vagrant'
|
||||
|
||||
dirs.split(File::PATH_SEPARATOR).each do |dir|
|
||||
gdir = dir.sub("C\:", "/C")
|
||||
vic_dev.vm.synced_folder dir, gdir
|
||||
end
|
||||
|
||||
vic_dev.vm.provider :virtualbox do |v, _override|
|
||||
v.memory = 4096
|
||||
v.cpus = 2
|
||||
end
|
||||
|
||||
[:vmware_fusion, :vmware_workstation].each do |visor|
|
||||
vic_dev.vm.provider visor do |v, _override|
|
||||
v.memory = 4096
|
||||
v.cpus = 2
|
||||
|
||||
v.vmx["ethernet0.pcislotnumber"] = "32"
|
||||
end
|
||||
end
|
||||
|
||||
Dir['infra/machines/devbox/provision.sh', 'infra/machines/devbox/provision-drone.sh'].each do |path|
|
||||
vic_dev.vm.provision 'shell', path: path, args: [gdir, vic_dev.ssh.username]
|
||||
end
|
||||
end
|
||||
end
|
||||
97
vendor/github.com/vmware/vic/cmd/archive/unpack.go
generated
vendored
Normal file
97
vendor/github.com/vmware/vic/cmd/archive/unpack.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
Success = iota
|
||||
WrongArgumentCount
|
||||
InvalidFilterSpec
|
||||
NoTarUnpackTarget
|
||||
UnpackTargetNotDirectory
|
||||
FailedChdirBeforeChroot
|
||||
FailedChroot
|
||||
FailedChdirAfterChroot
|
||||
FailedInvokeUnpack
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
op := trace.NewOperation(ctx, "Unpack") // TODO op ID?
|
||||
op.Debugf("New unpack operation created")
|
||||
|
||||
if len(os.Args) != 4 {
|
||||
op.Errorf("Wrong number of arguments passed to unpack binary")
|
||||
os.Exit(WrongArgumentCount)
|
||||
}
|
||||
|
||||
root := os.Args[2]
|
||||
|
||||
filterSpec, err := archive.DecodeFilterSpec(op, &os.Args[3])
|
||||
if err != nil {
|
||||
op.Errorf("Couldn't deserialize filterspec %s", os.Args[3])
|
||||
os.Exit(InvalidFilterSpec)
|
||||
}
|
||||
|
||||
fi, err := os.Stat(root)
|
||||
if err != nil {
|
||||
// the target unpack path does not exist. We should not get here.
|
||||
op.Errorf("tar unpack target does not exist: %s", root)
|
||||
os.Exit(NoTarUnpackTarget)
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
err := fmt.Errorf("unpack root target is not a directory: %s", root)
|
||||
op.Error(err)
|
||||
os.Exit(UnpackTargetNotDirectory)
|
||||
}
|
||||
op.Debugf("root exists: %s", root)
|
||||
|
||||
err = os.Chdir(root)
|
||||
if err != nil {
|
||||
op.Errorf("error while chdir outside chroot: %s", err.Error())
|
||||
os.Exit(FailedChdirBeforeChroot)
|
||||
}
|
||||
|
||||
err = syscall.Chroot(root)
|
||||
if err != nil {
|
||||
op.Errorf("error while chrootin': %s", err.Error())
|
||||
os.Exit(FailedChroot)
|
||||
}
|
||||
|
||||
err = os.Chdir("/")
|
||||
if err != nil {
|
||||
op.Errorf("error while chdir inside chroot: %s", err.Error())
|
||||
os.Exit(FailedChdirAfterChroot)
|
||||
}
|
||||
|
||||
if err = archive.InvokeUnpack(op, os.Stdin, filterSpec, "/"); err != nil {
|
||||
op.Error(err)
|
||||
os.Exit(FailedInvokeUnpack)
|
||||
}
|
||||
|
||||
os.Exit(Success)
|
||||
}
|
||||
320
vendor/github.com/vmware/vic/cmd/docker/main.go
generated
vendored
Normal file
320
vendor/github.com/vmware/vic/cmd/docker/main.go
generated
vendored
Normal file
@@ -0,0 +1,320 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
apiserver "github.com/docker/docker/api/server"
|
||||
"github.com/docker/docker/api/server/middleware"
|
||||
"github.com/docker/docker/api/server/router"
|
||||
"github.com/docker/docker/api/server/router/checkpoint"
|
||||
"github.com/docker/docker/api/server/router/container"
|
||||
"github.com/docker/docker/api/server/router/image"
|
||||
"github.com/docker/docker/api/server/router/network"
|
||||
"github.com/docker/docker/api/server/router/plugin"
|
||||
"github.com/docker/docker/api/server/router/swarm"
|
||||
"github.com/docker/docker/api/server/router/system"
|
||||
"github.com/docker/docker/api/server/router/volume"
|
||||
"github.com/docker/docker/daemon/cluster"
|
||||
"github.com/docker/docker/pkg/listeners"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
|
||||
vicbackends "github.com/vmware/vic/lib/apiservers/engine/backends"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/executor"
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/lib/pprof"
|
||||
viclog "github.com/vmware/vic/pkg/log"
|
||||
"github.com/vmware/vic/pkg/log/syslog"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
"github.com/vmware/vic/pkg/vsphere/tags"
|
||||
)
|
||||
|
||||
type CliOptions struct {
|
||||
serverPort *uint
|
||||
portLayerAddr *string
|
||||
portLayerPort *uint
|
||||
debug *bool
|
||||
|
||||
proto string
|
||||
}
|
||||
|
||||
const (
|
||||
productName = "vSphere Integrated Containers"
|
||||
)
|
||||
|
||||
var (
|
||||
vchConfig config.VirtualContainerHostConfigSpec
|
||||
cli CliOptions
|
||||
)
|
||||
|
||||
func init() {
|
||||
// #nosec: Errors unhandled.
|
||||
pprof.StartPprof("docker personality", pprof.DockerPort)
|
||||
|
||||
flag.Usage = Usage
|
||||
|
||||
_ = flag.String("serveraddr", "127.0.0.1", "Server address to listen") // ignored
|
||||
cli.serverPort = flag.Uint("port", 9000, "Port to listen")
|
||||
cli.portLayerAddr = flag.String("port-layer-addr", "127.0.0.1", "Port layer server address")
|
||||
cli.portLayerPort = flag.Uint("port-layer-port", 9001, "Port Layer server port")
|
||||
|
||||
cli.debug = flag.Bool("debug", false, "Enable debuglevel logging")
|
||||
}
|
||||
|
||||
func Usage() {
|
||||
// #nosec: Errors unhandled.
|
||||
fmt.Fprintf(os.Stderr, "\nvSphere Integrated Container Daemon Usage:\n")
|
||||
flag.PrintDefaults()
|
||||
// #nosec: Errors unhandled.
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Get flags
|
||||
ok := handleFlags()
|
||||
|
||||
if !ok {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := initLogging(); err != nil {
|
||||
log.Fatalf("failed to initialize logging: %s", err)
|
||||
}
|
||||
|
||||
if err := vicbackends.Init(*cli.portLayerAddr, productName, *cli.serverPort, &vchConfig); err != nil {
|
||||
log.Fatalf("failed to initialize backend: %s", err)
|
||||
}
|
||||
|
||||
plEventMonitor := vicbackends.NewPortlayerEventMonitor(vicbackends.PlEventProxy{}, vicbackends.DockerEventPublisher{})
|
||||
// #nosec: Errors unhandled.
|
||||
plEventMonitor.Start()
|
||||
// Start API server wit options from command line args
|
||||
api := startServer()
|
||||
|
||||
setAPIRoutes(api)
|
||||
|
||||
serveAPIWait := make(chan error)
|
||||
go api.Wait(serveAPIWait)
|
||||
|
||||
signal.Trap(func() {
|
||||
api.Close()
|
||||
})
|
||||
|
||||
<-serveAPIWait
|
||||
plEventMonitor.Stop()
|
||||
}
|
||||
|
||||
func handleFlags() bool {
|
||||
flag.Parse()
|
||||
|
||||
// load the vch config
|
||||
src, err := extraconfig.GuestInfoSource()
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to load configuration from guestinfo: %s", err)
|
||||
}
|
||||
extraconfig.Decode(src, &vchConfig)
|
||||
|
||||
if vchConfig.Diagnostics.DebugLevel > 2 {
|
||||
// use debug port layer mode
|
||||
*cli.portLayerPort = constants.DebugPortLayerPort
|
||||
clientIP, err := util.ClientIP()
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to look up %s for portlayer API: %s", constants.ClientHostName, err)
|
||||
}
|
||||
*cli.portLayerAddr = clientIP.String()
|
||||
}
|
||||
*cli.portLayerAddr = fmt.Sprintf("%s:%d", *cli.portLayerAddr, *cli.portLayerPort)
|
||||
cli.proto = "tcp"
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func initLogging() error {
|
||||
logcfg := viclog.NewLoggingConfig()
|
||||
if *cli.debug || vchConfig.Diagnostics.DebugLevel > 0 {
|
||||
logcfg.Level = log.DebugLevel
|
||||
trace.Logger.Level = log.DebugLevel
|
||||
syslog.Logger.Level = log.DebugLevel
|
||||
}
|
||||
|
||||
if vchConfig.Diagnostics.SysLogConfig != nil {
|
||||
logcfg.Syslog = &viclog.SyslogConfig{
|
||||
Network: vchConfig.Diagnostics.SysLogConfig.Network,
|
||||
RAddr: vchConfig.Diagnostics.SysLogConfig.RAddr,
|
||||
Priority: syslog.Info | syslog.Daemon,
|
||||
}
|
||||
}
|
||||
|
||||
tags.Logger = log.StandardLogger()
|
||||
|
||||
err := viclog.Init(logcfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return trace.InitLogger(logcfg)
|
||||
}
|
||||
|
||||
func loadCAPool() *x509.CertPool {
|
||||
// If we should verify the server, we need to load a trusted ca
|
||||
pool := x509.NewCertPool()
|
||||
|
||||
pem := vchConfig.CertificateAuthorities
|
||||
if len(pem) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !pool.AppendCertsFromPEM(vchConfig.CertificateAuthorities) {
|
||||
log.Fatalf("Unable to load CAs in config")
|
||||
}
|
||||
|
||||
log.Debugf("Loaded %d CAs from config", len(pool.Subjects()))
|
||||
return pool
|
||||
}
|
||||
|
||||
func startServer() *apiserver.Server {
|
||||
serverConfig := &apiserver.Config{
|
||||
Logging: true,
|
||||
Version: "1.22", //dockerversion.Version,
|
||||
}
|
||||
|
||||
// FIXME: assignment copies lock value to tlsConfig: crypto/tls.Config contains sync.Once contains sync.Mutex
|
||||
// #nosec: TLS InsecureSkipVerify may be true
|
||||
tlsConfig := func(c *tls.Config) *tls.Config {
|
||||
return &tls.Config{
|
||||
Certificates: c.Certificates,
|
||||
NameToCertificate: c.NameToCertificate,
|
||||
GetCertificate: c.GetCertificate,
|
||||
RootCAs: c.RootCAs,
|
||||
NextProtos: c.NextProtos,
|
||||
ServerName: c.ServerName,
|
||||
ClientAuth: c.ClientAuth,
|
||||
ClientCAs: c.ClientCAs,
|
||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||
CipherSuites: c.CipherSuites,
|
||||
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
||||
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
||||
SessionTicketKey: c.SessionTicketKey,
|
||||
ClientSessionCache: c.ClientSessionCache,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: c.MaxVersion,
|
||||
CurvePreferences: c.CurvePreferences,
|
||||
}
|
||||
}(tlsconfig.ServerDefault())
|
||||
|
||||
if !vchConfig.HostCertificate.IsNil() {
|
||||
log.Info("TLS enabled")
|
||||
|
||||
cert, err := vchConfig.HostCertificate.Certificate()
|
||||
if err != nil {
|
||||
// This is only viable because we've verified those certificates
|
||||
log.Fatalf("Could not load certificate from config and refusing to run without TLS with a host certificate specified: %s", err)
|
||||
}
|
||||
|
||||
tlsConfig.Certificates = []tls.Certificate{*cert}
|
||||
serverConfig.TLSConfig = tlsConfig
|
||||
|
||||
// Set options for TLS
|
||||
if len(vchConfig.CertificateAuthorities) > 0 {
|
||||
log.Info("Client verification enabled")
|
||||
// server requires and verifies client's certificate
|
||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
tlsConfig.ClientCAs = loadCAPool()
|
||||
tlsConfig.InsecureSkipVerify = false
|
||||
}
|
||||
}
|
||||
|
||||
addr := "0.0.0.0"
|
||||
// exposing this on all interfaces
|
||||
if vchConfig.Diagnostics.DebugLevel <= 2 {
|
||||
|
||||
// determine the address to listen on
|
||||
clientIP, err := util.ClientIP()
|
||||
if err != nil {
|
||||
// TODO: don't want to directly enter this into vchConfig.Sessions[].Started but no
|
||||
// structure currently to report back contents otherwise
|
||||
log.Fatalf("Unable to look up %s to serve docker API: %s", constants.ClientHostName, err)
|
||||
}
|
||||
|
||||
addr = clientIP.String()
|
||||
}
|
||||
|
||||
api := apiserver.New(serverConfig)
|
||||
mw := middleware.NewVersionMiddleware(version.DockerAPIVersion,
|
||||
version.DockerDefaultVersion,
|
||||
version.DockerMinimumVersion)
|
||||
api.UseMiddleware(mw)
|
||||
fullserver := fmt.Sprintf("%s:%d", addr, *cli.serverPort)
|
||||
l, err := listeners.Init(cli.proto, fullserver, "", serverConfig.TLSConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Printf("Listener created for HTTP on %s//%s", addr, cli.proto)
|
||||
api.Accept(fullserver, l...)
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
func setAPIRoutes(api *apiserver.Server) {
|
||||
decoder := runconfig.ContainerDecoder{}
|
||||
|
||||
swarmBackend := executor.SwarmBackend{}
|
||||
|
||||
c, err := cluster.New(cluster.Config{
|
||||
Root: "",
|
||||
Name: "",
|
||||
Backend: swarmBackend,
|
||||
NetworkSubnetsProvider: nil,
|
||||
DefaultAdvertiseAddr: "",
|
||||
RuntimeRoot: "",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating cluster component: %v", err)
|
||||
}
|
||||
|
||||
routers := []router.Router{
|
||||
image.NewRouter(vicbackends.NewImageBackend(), decoder),
|
||||
container.NewRouter(vicbackends.NewContainerBackend(), decoder),
|
||||
volume.NewRouter(vicbackends.NewVolumeBackend()),
|
||||
network.NewRouter(vicbackends.NewNetworkBackend(), c),
|
||||
system.NewRouter(vicbackends.NewSystemBackend(), c),
|
||||
swarm.NewRouter(vicbackends.NewSwarmBackend()),
|
||||
checkpoint.NewRouter(vicbackends.NewCheckpointBackend(), decoder),
|
||||
plugin.NewRouter(vicbackends.NewPluginBackend()),
|
||||
}
|
||||
|
||||
for _, r := range routers {
|
||||
for _, route := range r.Routes() {
|
||||
if experimental, ok := route.(router.ExperimentalRoute); ok {
|
||||
experimental.Enable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api.InitRouter(false, routers...)
|
||||
}
|
||||
38
vendor/github.com/vmware/vic/cmd/docker/main_test.go
generated
vendored
Normal file
38
vendor/github.com/vmware/vic/cmd/docker/main_test.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2016-2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var systemTest *bool
|
||||
|
||||
func init() {
|
||||
systemTest = flag.Bool("systemTest", false, "Run system test")
|
||||
}
|
||||
|
||||
func TestSystem(t *testing.T) {
|
||||
if *systemTest {
|
||||
main()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleFlags(t *testing.T) {
|
||||
assert.True(t, handleFlags(), "Flags were not parsed correctly")
|
||||
}
|
||||
366
vendor/github.com/vmware/vic/cmd/gandalf/main.go
generated
vendored
Normal file
366
vendor/github.com/vmware/vic/cmd/gandalf/main.go
generated
vendored
Normal file
@@ -0,0 +1,366 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/nlopes/slack"
|
||||
)
|
||||
|
||||
const (
|
||||
GandalfContext = "gandalf/thebot"
|
||||
PullApproveContext = "code-review/pullapprove"
|
||||
// ExistingContext is what we replace context with (we go from "gandalf/thebot", "code-review/pullapprove" to this)
|
||||
ExistingContext = `["code-review/pullapprove"]`
|
||||
|
||||
GitHubStatusAPIURL = "https://status.github.com/api/last-message.json"
|
||||
GitHubContextsAPIURL = "https://api.github.com/repos/%s/%s/branches/%s/protection/required_status_checks/contexts"
|
||||
|
||||
DefaultUser = "vmware"
|
||||
DefaultRepo = "vic"
|
||||
DefaultBranch = "master"
|
||||
|
||||
DefaultFellows = "mhagen,mwilliamson,ghicken"
|
||||
|
||||
// 4 disabling merges to master automagically
|
||||
DroneFailureMessage = "finished with a failure status, find the logs"
|
||||
)
|
||||
|
||||
var (
|
||||
config = GandalfConfig{}
|
||||
)
|
||||
|
||||
type ActionType func(msg *slack.MessageEvent) string
|
||||
|
||||
type MultipleVar []string
|
||||
|
||||
func (i *MultipleVar) String() string {
|
||||
return fmt.Sprint(*i)
|
||||
}
|
||||
|
||||
func (i *MultipleVar) Set(value string) error {
|
||||
*i = append(*i, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
type GandalfConfig struct {
|
||||
GithubToken string
|
||||
SlackToken string
|
||||
|
||||
User string
|
||||
Repo string
|
||||
Branch string
|
||||
|
||||
Fellows MultipleVar
|
||||
}
|
||||
|
||||
type Gandalf struct {
|
||||
GandalfConfig
|
||||
|
||||
slack *slack.Client
|
||||
github *github.Client
|
||||
rtm *slack.RTM
|
||||
|
||||
// gandalf's prefix
|
||||
prefix string
|
||||
|
||||
// name->id cache
|
||||
u2i map[string]string
|
||||
|
||||
// text->fn
|
||||
actions map[string]ActionType
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
config.SlackToken = os.Getenv("SLACK_TOKEN")
|
||||
if config.SlackToken == "" {
|
||||
panic("missing slack token")
|
||||
}
|
||||
config.GithubToken = os.Getenv("GITHUB_TOKEN")
|
||||
if config.GithubToken == "" {
|
||||
panic("missing github token")
|
||||
}
|
||||
|
||||
flag.StringVar(&config.User, "user", DefaultUser, "Name of the user")
|
||||
flag.StringVar(&config.Repo, "repo", DefaultRepo, "Name of the repo")
|
||||
flag.StringVar(&config.Branch, "branch", DefaultBranch, "Name of the branch")
|
||||
|
||||
flag.Var(&config.Fellows, "fellow", "Fellow name")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// use the default if no fellow given
|
||||
if len(config.Fellows) == 0 {
|
||||
config.Fellows = strings.Split(DefaultFellows, ",")
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gandalf) Setup() {
|
||||
ctx := context.Background()
|
||||
|
||||
ts := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: g.GithubToken},
|
||||
)
|
||||
tc := oauth2.NewClient(ctx, ts)
|
||||
|
||||
g.github = github.NewClient(tc)
|
||||
g.slack = slack.New(g.SlackToken)
|
||||
|
||||
g.rtm = g.slack.NewRTM()
|
||||
|
||||
g.actions = map[string]ActionType{
|
||||
"status?": func(msg *slack.MessageEvent) string {
|
||||
ok, err := g.closed()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Something bad happened: %s", err)
|
||||
}
|
||||
if ok {
|
||||
return "Merges to master are closed..."
|
||||
}
|
||||
return "Merges to master are open..."
|
||||
},
|
||||
"disable merges to master": func(msg *slack.MessageEvent) string {
|
||||
if !g.fellow(msg.User) {
|
||||
return "You are not a fellow. You can't hold the ring."
|
||||
}
|
||||
if err := g.disable(); err != nil {
|
||||
return fmt.Sprintf("Something bad happened: %s", err)
|
||||
}
|
||||
return "One moment, I'll call the Github CEO to do that"
|
||||
},
|
||||
"enable merges to master": func(msg *slack.MessageEvent) string {
|
||||
if !g.fellow(msg.User) {
|
||||
return "You are not a fellow. You can't hold the ring."
|
||||
}
|
||||
if err := g.enable(); err != nil {
|
||||
return fmt.Sprintf("Something bad happened: %s", err)
|
||||
}
|
||||
return "Sure but it will cost you"
|
||||
},
|
||||
"who are you?": func(msg *slack.MessageEvent) string {
|
||||
return "I am Gandalf the Bot. You shall not merge..."
|
||||
},
|
||||
"what is your story?": func(msg *slack.MessageEvent) string {
|
||||
return "I used my last measure of strength to revert some commits. My spirit then left my body, having sacrificed myself to save the project. My spirit did not depart Github forever at this time. As the only one of the five Istari to stay true to his errand, I was sent back to mortal lands by Eru, and I became Gandalf the Bot"
|
||||
},
|
||||
"where are you?": func(msg *slack.MessageEvent) string {
|
||||
return "Hmmm, somewhere over the rainbow. I'm meeting with VCs to raise some capital for my new blockchain startup. Chains are the new rings."
|
||||
},
|
||||
"how are you doing?": func(msg *slack.MessageEvent) string {
|
||||
return "Ash nazg durbatulûk, ash nazg gimbatul,\nAsh nazg thrakatulûk agh burzum-ishi krimpatul."
|
||||
},
|
||||
"what do you think about scrum?": func(msg *slack.MessageEvent) string {
|
||||
return "One sprint to rule them all, one sprint to find them,\nOne sprint to bring them all and in the darkness bind them."
|
||||
},
|
||||
"how is github doing?": func(msg *slack.MessageEvent) string { return g.status() },
|
||||
"show me open prs": func(msg *slack.MessageEvent) string { return g.pr() },
|
||||
"show me the fellows": func(msg *slack.MessageEvent) string { return g.Fellows.String() },
|
||||
}
|
||||
|
||||
// manage the connections
|
||||
go g.rtm.ManageConnection()
|
||||
|
||||
// populate the cache
|
||||
users, err := g.slack.GetUsers()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to fetch users: %s", err))
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
g.u2i[user.Name] = user.ID
|
||||
}
|
||||
|
||||
g.prefix = fmt.Sprintf("<@%s> ", g.u2i["gandalf"])
|
||||
}
|
||||
|
||||
func main() {
|
||||
gandalf := Gandalf{
|
||||
GandalfConfig: config,
|
||||
u2i: make(map[string]string),
|
||||
actions: make(map[string]ActionType),
|
||||
}
|
||||
|
||||
gandalf.Setup()
|
||||
|
||||
// wait 4 events
|
||||
for msg := range gandalf.rtm.IncomingEvents {
|
||||
switch ev := msg.Data.(type) {
|
||||
case *slack.MessageEvent:
|
||||
gandalf.handle(ev)
|
||||
case *slack.RTMError:
|
||||
fmt.Printf("Error: %s\n", ev.Error())
|
||||
|
||||
case *slack.InvalidAuthEvent:
|
||||
fmt.Printf("Invalid credentials")
|
||||
return
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gandalf) status() string {
|
||||
var status struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"body"`
|
||||
Created time.Time `json:"created_on"`
|
||||
}
|
||||
|
||||
resp, err := http.Get(GitHubStatusAPIURL)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Err: %s", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
err = json.Unmarshal(body, &status)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Err: %s", err)
|
||||
}
|
||||
return fmt.Sprintf("Current Github Status : %s. Message : %s Reported at %s", status.Status, status.Message, status.Created)
|
||||
}
|
||||
|
||||
func (g *Gandalf) fellow(id string) bool {
|
||||
// linear search bla bla
|
||||
for i := range g.Fellows {
|
||||
if id == g.u2i[g.Fellows[i]] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *Gandalf) pr() string {
|
||||
ctx := context.Background()
|
||||
|
||||
opt := &github.PullRequestListOptions{Sort: "created", Direction: "desc"}
|
||||
|
||||
prs, _, err := g.github.PullRequests.List(ctx, g.User, g.Repo, opt)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Err: %s", err)
|
||||
}
|
||||
|
||||
response := ""
|
||||
for i := range prs {
|
||||
response = fmt.Sprintf("%s %d - %s (%s)\n", response, *prs[i].Number, *prs[i].Title, *prs[i].HTMLURL)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func (g *Gandalf) closed() (bool, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
protection, _, err := g.github.Repositories.GetBranchProtection(ctx, g.User, g.Repo, g.Branch)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for i := range protection.RequiredStatusChecks.Contexts {
|
||||
if protection.RequiredStatusChecks.Contexts[i] == GandalfContext {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (g *Gandalf) disable() error {
|
||||
ctx := context.Background()
|
||||
|
||||
preq := &github.ProtectionRequest{
|
||||
RequiredStatusChecks: &github.RequiredStatusChecks{
|
||||
IncludeAdmins: false,
|
||||
Strict: true,
|
||||
Contexts: []string{GandalfContext, PullApproveContext},
|
||||
},
|
||||
}
|
||||
_, _, err := g.github.Repositories.UpdateBranchProtection(ctx, g.User, g.Repo, g.Branch, preq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Gandalf) enable() error {
|
||||
// https://developer.github.com/v3/repos/branches/#replace-required-status-checks-contexts-of-protected-branch is not supported by the Go github API yet so we improvise
|
||||
url := fmt.Sprintf(GitHubContextsAPIURL, g.User, g.Repo, g.Branch)
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
req, _ := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(ExistingContext)))
|
||||
req.Header.Set("Authorization", fmt.Sprintf("token %s", g.GithubToken))
|
||||
req.Header.Set("Accept", "application/vnd.github.loki-preview+json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("%#v", resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Gandalf) handle(msg *slack.MessageEvent) {
|
||||
// is it drone failure?
|
||||
if strings.Contains(msg.Text, DroneFailureMessage) {
|
||||
if err := g.disable(); err != nil {
|
||||
g.rtm.SendMessage(g.rtm.NewOutgoingMessage(fmt.Sprintf("Something bad happened: %s", err), msg.Channel))
|
||||
}
|
||||
g.rtm.SendMessage(g.rtm.NewOutgoingMessage("They shall not merge! Closing master to merges...", msg.Channel))
|
||||
return
|
||||
}
|
||||
|
||||
// @gandalf TEXT
|
||||
if strings.HasPrefix(msg.Text, g.prefix) {
|
||||
text := msg.Text
|
||||
text = strings.TrimPrefix(text, g.prefix)
|
||||
text = strings.TrimSpace(text)
|
||||
text = strings.ToLower(text)
|
||||
|
||||
if response, ok := g.actions[text]; ok {
|
||||
g.rtm.SendMessage(g.rtm.NewOutgoingMessage(response(msg), msg.Channel))
|
||||
return
|
||||
}
|
||||
|
||||
cmds := make([]string, 0, len(g.actions))
|
||||
for key := range g.actions {
|
||||
cmds = append(cmds, key)
|
||||
}
|
||||
g.rtm.SendMessage(g.rtm.NewOutgoingMessage(
|
||||
fmt.Sprintf("Sorry, I can't help with that yet. I can understand following;\n```%s```\n", strings.Join(cmds, "\n")),
|
||||
msg.Channel,
|
||||
))
|
||||
}
|
||||
}
|
||||
47
vendor/github.com/vmware/vic/cmd/imagec/hook.go
generated
vendored
Normal file
47
vendor/github.com/vmware/vic/cmd/imagec/hook.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ErrorHook is a simple logrus hook to duplicate Fatalf leveled error messages to stderr
|
||||
type ErrorHook struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// NewErrorHook returns an empty ErrorHook struct
|
||||
func NewErrorHook(w io.Writer) *ErrorHook {
|
||||
return &ErrorHook{w: w}
|
||||
}
|
||||
|
||||
// Fire dumps the entry.Message to Stderr
|
||||
func (hook *ErrorHook) Fire(entry *logrus.Entry) error {
|
||||
err := fmt.Errorf("%s", entry.Message)
|
||||
fmt.Fprintf(hook.w, string(sf.FormatError(err)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Levels returns the logging level that we want to subscribe
|
||||
func (hook *ErrorHook) Levels() []logrus.Level {
|
||||
return []logrus.Level{
|
||||
logrus.FatalLevel,
|
||||
}
|
||||
}
|
||||
330
vendor/github.com/vmware/vic/cmd/imagec/main.go
generated
vendored
Normal file
330
vendor/github.com/vmware/vic/cmd/imagec/main.go
generated
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"runtime/debug"
|
||||
"runtime/trace"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/go-openapi/runtime"
|
||||
rc "github.com/go-openapi/runtime/client"
|
||||
"github.com/pkg/profile"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
apiclient "github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
vicarchive "github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/imagec"
|
||||
optrace "github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
)
|
||||
|
||||
var (
|
||||
imageCOptions = ImageCOptions{}
|
||||
|
||||
// https://raw.githubusercontent.com/docker/docker/master/distribution/pull_v2.go
|
||||
sf = streamformatter.NewJSONStreamFormatter()
|
||||
)
|
||||
|
||||
const (
|
||||
PullImage = "pull"
|
||||
PushImage = "push"
|
||||
ListLayers = "listlayers"
|
||||
Save = "save"
|
||||
)
|
||||
|
||||
// ImageCOptions wraps the cli arguments
|
||||
type ImageCOptions struct {
|
||||
imageName string
|
||||
|
||||
options imagec.Options
|
||||
|
||||
logfile string
|
||||
|
||||
stdout bool
|
||||
debug bool
|
||||
|
||||
profiling string
|
||||
tracing bool
|
||||
|
||||
operation string
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
flag.StringVar(&imageCOptions.imageName, "reference", "", "Name of the reference")
|
||||
|
||||
flag.StringVar(&imageCOptions.options.Destination, "destination", imagec.DefaultDestination, "Destination directory")
|
||||
|
||||
flag.StringVar(&imageCOptions.options.Host, "host", imagec.DefaultPortLayerHost, "Host that runs portlayer API (FQDN:port format)")
|
||||
|
||||
flag.StringVar(&imageCOptions.logfile, "logfile", imagec.DefaultLogfile, "Path of the imagec log file")
|
||||
|
||||
flag.StringVar(&imageCOptions.options.Username, "username", "", "Username")
|
||||
flag.StringVar(&imageCOptions.options.Password, "password", "", "Password")
|
||||
|
||||
flag.DurationVar(&imageCOptions.options.Timeout, "timeout", imagec.DefaultHTTPTimeout, "HTTP timeout")
|
||||
|
||||
flag.BoolVar(&imageCOptions.stdout, "stdout", false, "Enable writing to stdout")
|
||||
flag.BoolVar(&imageCOptions.debug, "debug", false, "Show debug logging")
|
||||
flag.BoolVar(&imageCOptions.options.InsecureSkipVerify, "insecure-skip-verify", false, "Don't verify certificates when fetching images")
|
||||
flag.BoolVar(&imageCOptions.options.InsecureAllowHTTP, "insecure-allow-http", false, "Uses unencrypted connections when fetching images")
|
||||
flag.BoolVar(&imageCOptions.options.Standalone, "standalone", false, "Disable port-layer integration")
|
||||
|
||||
flag.StringVar(&imageCOptions.profiling, "profile.mode", "", "Enable profiling mode, one of [cpu, mem, block]")
|
||||
flag.BoolVar(&imageCOptions.tracing, "tracing", false, "Enable runtime tracing")
|
||||
|
||||
flag.StringVar(&imageCOptions.operation, "operation", "pull", "Pull image/push image/listlayers/save image")
|
||||
|
||||
flag.StringVar(&imageCOptions.options.Registry, "registry", imagec.DefaultDockerURL, "Registry to pull/push images (default: registry-1.docker.io)")
|
||||
|
||||
flag.StringVar(&imageCOptions.options.ImageStore, "image-store", imagec.DefaultDockerURL, "portlayer image store name or url used to query image data")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
var err error
|
||||
if imageCOptions.options.Reference, err = reference.ParseNamed(imageCOptions.imageName); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
_, err := fmt.Fprintf(os.Stderr, string(sf.FormatError(fmt.Errorf("%s : %s", r, debug.Stack()))))
|
||||
if err != nil {
|
||||
//do this to pass security check
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if version.Show() {
|
||||
_, err := fmt.Fprintf(os.Stdout, "%s\n", version.String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Enable profiling if mode is set
|
||||
switch imageCOptions.profiling {
|
||||
case "cpu":
|
||||
defer profile.Start(profile.CPUProfile, profile.ProfilePath("."), profile.Quiet).Stop()
|
||||
case "mem":
|
||||
defer profile.Start(profile.MemProfile, profile.ProfilePath("."), profile.Quiet).Stop()
|
||||
case "block":
|
||||
defer profile.Start(profile.BlockProfile, profile.ProfilePath("."), profile.Quiet).Stop()
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// Register our custom Error hook
|
||||
log.AddHook(NewErrorHook(os.Stderr))
|
||||
|
||||
// Enable runtime tracing if tracing is true
|
||||
if imageCOptions.tracing {
|
||||
tracing, err := os.Create(time.Now().Format("2006-01-02T150405.pprof"))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create tracing logfile: %s", err)
|
||||
}
|
||||
defer tracing.Close()
|
||||
|
||||
if err := trace.Start(tracing); err != nil {
|
||||
log.Fatalf("Failed to start tracing: %s", err)
|
||||
}
|
||||
defer trace.Stop()
|
||||
}
|
||||
|
||||
// Open the log file
|
||||
// #nosec: Expect file permissions to be 0600 or less
|
||||
f, err := os.OpenFile(imageCOptions.logfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open the logfile %s: %s", imageCOptions.logfile, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Initiliaze logger with default TextFormatter
|
||||
log.SetFormatter(&log.TextFormatter{DisableColors: true, FullTimestamp: true})
|
||||
|
||||
// Set the log level
|
||||
if imageCOptions.debug {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
// SetOutput to log file and/or stdout
|
||||
log.SetOutput(f)
|
||||
if imageCOptions.stdout {
|
||||
log.SetOutput(io.MultiWriter(os.Stdout, f))
|
||||
}
|
||||
|
||||
switch imageCOptions.operation {
|
||||
case PullImage:
|
||||
options := imageCOptions.options
|
||||
|
||||
options.Outstream = os.Stdout
|
||||
|
||||
ic := imagec.NewImageC(options, streamformatter.NewJSONStreamFormatter())
|
||||
if err := ic.PullImage(); err != nil {
|
||||
log.Fatalf("Pulling image failed due to %s\n", err)
|
||||
}
|
||||
case PushImage:
|
||||
log.Errorf("The operation '%s' is not implemented\n", PushImage)
|
||||
case ListLayers:
|
||||
options := imageCOptions.options
|
||||
|
||||
options.Outstream = os.Stdout
|
||||
|
||||
ic := imagec.NewImageC(options, streamformatter.NewJSONStreamFormatter())
|
||||
if err := ic.ListLayers(); err != nil {
|
||||
log.Fatalf("Listing layers for image failed due to %s\n", err)
|
||||
}
|
||||
case Save:
|
||||
options := imageCOptions.options
|
||||
|
||||
options.Outstream = os.Stdout
|
||||
|
||||
ap := archiveProxy(options.Host)
|
||||
ic := imagec.NewImageC(options, streamformatter.NewJSONStreamFormatter())
|
||||
if err := saveImage(ap, ic); err != nil {
|
||||
log.Fatalf("Saving image %s failed due to %s\n", options.Reference.String(), err)
|
||||
}
|
||||
default:
|
||||
log.Errorf("The operation '%s' is not valid\n", imageCOptions.operation)
|
||||
}
|
||||
}
|
||||
|
||||
func saveImage(ap proxy.VicArchiveProxy, ic *imagec.ImageC) error {
|
||||
log.Debugf("Save image %s", ic.Options.Reference)
|
||||
err := ic.ListLayers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
layers, err := ic.LayersToDownload()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest := imagec.DestinationDirectory(ic.Options)
|
||||
var wg sync.WaitGroup
|
||||
errChan := make(chan error, len(layers))
|
||||
|
||||
for i := len(layers) - 1; i >= 0; i-- {
|
||||
if layers[i].ID == constants.ScratchLayerID {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(pid, cid string) {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
log.Debugf("parent id: %s, layer id: %s", pid, cid)
|
||||
|
||||
defer func() {
|
||||
errChan <- err
|
||||
}()
|
||||
|
||||
fileDir := path.Join(dest, cid)
|
||||
err = os.MkdirAll(fileDir, 0755) /* #nosec */
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filePath := path.Join(fileDir, cid+".tar")
|
||||
log.Debugf("save layer %s to file %s", cid, filePath)
|
||||
err = writeArchiveFile(ap, ic.ImageStore, ic.ImageStore, cid, pid, filePath)
|
||||
}(layers[i].Parent, layers[i].ID)
|
||||
}
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
|
||||
var errs []string
|
||||
for e := range errChan {
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
errs = append(errs, e.Error())
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("save image layers failed: %s", strings.Join(errs, ","))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func portlayerClient(portLayerAddr string) *apiclient.PortLayer {
|
||||
t := rc.New(portLayerAddr, "/", []string{"http"})
|
||||
t.Consumers["application/x-tar"] = runtime.ByteStreamConsumer()
|
||||
t.Consumers["application/octet-stream"] = runtime.ByteStreamConsumer()
|
||||
t.Producers["application/x-tar"] = runtime.ByteStreamProducer()
|
||||
t.Producers["application/octet-stream"] = runtime.ByteStreamProducer()
|
||||
|
||||
client := apiclient.New(t, nil)
|
||||
return client
|
||||
}
|
||||
|
||||
func archiveProxy(portLayerAddr string) proxy.VicArchiveProxy {
|
||||
plClient := portlayerClient(portLayerAddr)
|
||||
archiveProxy := proxy.NewArchiveProxy(plClient)
|
||||
|
||||
return archiveProxy
|
||||
}
|
||||
|
||||
func writeArchiveFile(archiveProxy proxy.VicArchiveProxy, store, ancestorStore, layerID, ancestorID, archivePath string) error {
|
||||
var filterSpec vicarchive.FilterSpec
|
||||
|
||||
op := optrace.NewOperation(context.Background(), "export layer %s:%s", layerID, ancestorID)
|
||||
//Initialize an archive stream from the portlayer for the layer
|
||||
ar, err := archiveProxy.ArchiveExportReader(op, store, ancestorStore, layerID, ancestorID, true, filterSpec)
|
||||
if err != nil || ar == nil {
|
||||
return fmt.Errorf("Failed to get reader for layer %s", layerID)
|
||||
}
|
||||
|
||||
log.Infof("Obtain archive reader for layer %s, parent %s", layerID, ancestorID)
|
||||
|
||||
// #nosec - there's nothing secret/sensitive about these image layer files
|
||||
tarFile, err := os.OpenFile(archivePath, os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to create tmp file: %s", err.Error())
|
||||
log.Info(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
defer tarFile.Close()
|
||||
|
||||
_, err = io.Copy(tarFile, ar)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to read from acrhive stream: %s", err.Error())
|
||||
log.Info(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
ar.Close()
|
||||
|
||||
if err = tarFile.Sync(); err != nil {
|
||||
msg := fmt.Sprintf("Failed to flush tar file: %s", err.Error())
|
||||
log.Info(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
123
vendor/github.com/vmware/vic/cmd/kubelet-starter/main.go
generated
vendored
Normal file
123
vendor/github.com/vmware/vic/cmd/kubelet-starter/main.go
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2016-2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
viclog "github.com/vmware/vic/pkg/log"
|
||||
"github.com/vmware/vic/pkg/log/syslog"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
var (
|
||||
vchConfig config.VirtualContainerHostConfigSpec
|
||||
)
|
||||
|
||||
const (
|
||||
KubeletConfigFile = "/etc/kubelet.conf"
|
||||
KubeletPath = "/sbin/virtual-kubelet"
|
||||
Provider = "vic"
|
||||
)
|
||||
|
||||
func main() {
|
||||
op := trace.NewOperation(context.Background(), "kubelet-starter")
|
||||
|
||||
// load the vch config
|
||||
src, err := extraconfig.GuestInfoSource()
|
||||
if err != nil {
|
||||
op.Fatalf("Unable to load configuration from guestinfo: %s", err)
|
||||
}
|
||||
extraconfig.Decode(src, &vchConfig)
|
||||
|
||||
logcfg := viclog.NewLoggingConfig()
|
||||
if vchConfig.Diagnostics.DebugLevel > 0 {
|
||||
logcfg.Level = log.DebugLevel
|
||||
trace.Logger.Level = log.DebugLevel
|
||||
syslog.Logger.Level = log.DebugLevel
|
||||
}
|
||||
|
||||
if vchConfig.Diagnostics.DebugLevel > 3 {
|
||||
// extraconfig is very, very verbose
|
||||
extraconfig.SetLogLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
if vchConfig.Diagnostics.SysLogConfig != nil {
|
||||
logcfg.Syslog = &viclog.SyslogConfig{
|
||||
Network: vchConfig.Diagnostics.SysLogConfig.Network,
|
||||
RAddr: vchConfig.Diagnostics.SysLogConfig.RAddr,
|
||||
Priority: syslog.Info | syslog.Daemon,
|
||||
}
|
||||
}
|
||||
|
||||
op.Infof("%+v", *logcfg)
|
||||
// #nosec: Errors unhandled.
|
||||
viclog.Init(logcfg)
|
||||
trace.InitLogger(logcfg)
|
||||
|
||||
// Get the port numbers for Docker and Portlayer
|
||||
personaPort := os.Getenv("PERSONA_PORT")
|
||||
portlayerPort := os.Getenv("PORTLAYER_PORT")
|
||||
|
||||
if vchConfig.Diagnostics.DebugLevel > 2 {
|
||||
// expose portlayer service on client interface
|
||||
portlayerPort = strconv.Itoa(constants.DebugPortLayerPort)
|
||||
}
|
||||
|
||||
clientIP, err := util.ClientIP()
|
||||
|
||||
if err != nil {
|
||||
op.Fatalf("Cannot get Client IP err: %s", err)
|
||||
}
|
||||
|
||||
personaAddr := fmt.Sprintf("%s:%s", clientIP, personaPort)
|
||||
portlayerAddr := fmt.Sprintf("localhost:%s", portlayerPort)
|
||||
|
||||
os.Setenv("PERSONA_ADDR", personaAddr)
|
||||
os.Setenv("PORTLAYER_ADDR", portlayerAddr)
|
||||
|
||||
op.Infof("KUBELET_NAME = %s", os.Getenv("KUBELET_NAME"))
|
||||
op.Infof("KUBERNETES_SERVICE_HOST = %s", os.Getenv("KUBERNETES_SERVICE_HOST"))
|
||||
op.Infof("KUBERNETES_SERVICE_PORT = %s", os.Getenv("KUBERNETES_SERVICE_PORT"))
|
||||
op.Infof("PERSONA_ADDR = %s", os.Getenv("PERSONA_ADDR"))
|
||||
op.Infof("PORTLAYER_ADDR = %s", os.Getenv("PORTLAYER_ADDR"))
|
||||
|
||||
// Create Kubelet config file
|
||||
content := []byte(vchConfig.Kubelet.KubeletConfigContent)
|
||||
err = ioutil.WriteFile(KubeletConfigFile, content, 0644)
|
||||
|
||||
if err != nil {
|
||||
op.Fatalf("Cannot write Kubelet config file to: %s, err: %s", KubeletConfigFile, err)
|
||||
}
|
||||
|
||||
kubeletName := os.Getenv("KUBELET_NAME")
|
||||
|
||||
op.Infof("Executing kubelet: %s %s %s %s %s %s %s", KubeletPath, "--provider", Provider, "--kubeconfig", KubeletConfigFile, "--nodename", kubeletName)
|
||||
/* #nosec */
|
||||
kubeletCmd := exec.Command(KubeletPath, "--provider", Provider, "--kubeconfig", KubeletConfigFile, "--nodename", kubeletName)
|
||||
output, err := kubeletCmd.CombinedOutput()
|
||||
op.Infof("Output: %s, Error: %s", string(output), err)
|
||||
}
|
||||
15
vendor/github.com/vmware/vic/cmd/kubelet-starter/main_test.go
generated
vendored
Normal file
15
vendor/github.com/vmware/vic/cmd/kubelet-starter/main_test.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2016-2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
165
vendor/github.com/vmware/vic/cmd/port-layer-server/main.go
generated
vendored
Normal file
165
vendor/github.com/vmware/vic/cmd/port-layer-server/main.go
generated
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/go-openapi/loads"
|
||||
"github.com/jessevdk/go-flags"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/restapi"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/restapi/operations"
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/dns"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/lib/pprof"
|
||||
"github.com/vmware/vic/lib/vspc"
|
||||
viclog "github.com/vmware/vic/pkg/log"
|
||||
"github.com/vmware/vic/pkg/log/syslog"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
var (
|
||||
options = dns.ServerOptions{}
|
||||
parser *flags.Parser
|
||||
server *restapi.Server
|
||||
vchConfig config.VirtualContainerHostConfigSpec
|
||||
)
|
||||
|
||||
func init() {
|
||||
pprof.StartPprof("portlayer server", pprof.PortlayerPort)
|
||||
|
||||
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
api := operations.NewPortLayerAPI(swaggerSpec)
|
||||
server = restapi.NewServer(api)
|
||||
|
||||
parser = flags.NewParser(server, flags.Default)
|
||||
parser.ShortDescription = `Port Layer API`
|
||||
parser.LongDescription = `Port Layer API`
|
||||
|
||||
server.ConfigureFlags()
|
||||
|
||||
for _, optsGroup := range api.CommandLineOptionsGroups {
|
||||
_, err := parser.AddGroup(optsGroup.ShortDescription, optsGroup.LongDescription, optsGroup.Options)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
if _, err := parser.Parse(); err != nil {
|
||||
if err := err.(*flags.Error); err != nil && err.Type == flags.ErrHelp {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// load the vch config
|
||||
src, err := extraconfig.GuestInfoSource()
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to load configuration from guestinfo: %s", err)
|
||||
}
|
||||
extraconfig.Decode(src, &vchConfig)
|
||||
|
||||
logcfg := viclog.NewLoggingConfig()
|
||||
if vchConfig.Diagnostics.DebugLevel > 0 {
|
||||
logcfg.Level = log.DebugLevel
|
||||
trace.Logger.Level = log.DebugLevel
|
||||
syslog.Logger.Level = log.DebugLevel
|
||||
}
|
||||
|
||||
if vchConfig.Diagnostics.DebugLevel > 3 {
|
||||
// extraconfig is very, very verbose
|
||||
extraconfig.SetLogLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
if vchConfig.Diagnostics.SysLogConfig != nil {
|
||||
logcfg.Syslog = &viclog.SyslogConfig{
|
||||
Network: vchConfig.Diagnostics.SysLogConfig.Network,
|
||||
RAddr: vchConfig.Diagnostics.SysLogConfig.RAddr,
|
||||
Priority: syslog.Info | syslog.Daemon,
|
||||
}
|
||||
}
|
||||
|
||||
if vchConfig.Diagnostics.DebugLevel > 2 {
|
||||
// expose portlayer service on client interface
|
||||
server.Port = constants.DebugPortLayerPort
|
||||
clientIP, err := util.ClientIP()
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to look up %s to serve portlayer API: %s", constants.ClientHostName, err)
|
||||
}
|
||||
server.Host = clientIP.String()
|
||||
}
|
||||
|
||||
log.Infof("%+v", *logcfg)
|
||||
// #nosec: Errors unhandled.
|
||||
viclog.Init(logcfg)
|
||||
trace.InitLogger(logcfg)
|
||||
|
||||
server.ConfigureAPI()
|
||||
|
||||
// BEGIN
|
||||
// Set the Interface name to instruct listeners to bind on this interface
|
||||
options.Interface = "bridge"
|
||||
|
||||
// Start the DNS Server
|
||||
dnsserver := dns.NewServer(options)
|
||||
if dnsserver != nil {
|
||||
dnsserver.Start()
|
||||
}
|
||||
|
||||
// handle the signals and gracefully shutdown the server
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
defer server.Shutdown()
|
||||
|
||||
vspc := vspc.NewVspc()
|
||||
|
||||
if vspc == nil {
|
||||
log.Fatalln("cannot initialize virtual serial port concentrator")
|
||||
}
|
||||
vspc.Start()
|
||||
|
||||
go func() {
|
||||
<-sig
|
||||
|
||||
vspc.Stop()
|
||||
dnsserver.Stop()
|
||||
restapi.StopAPIServers()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
dnsserver.Wait()
|
||||
}()
|
||||
// END
|
||||
|
||||
if err := server.Serve(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
140
vendor/github.com/vmware/vic/cmd/port-layer-server/main_test.go
generated
vendored
Normal file
140
vendor/github.com/vmware/vic/cmd/port-layer-server/main_test.go
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
flags "github.com/jessevdk/go-flags"
|
||||
)
|
||||
|
||||
var systemTest *bool
|
||||
var flagArgs []string
|
||||
|
||||
// testOptions is the list of options used by the standard testing package in a format
|
||||
// that the go-flags package can understand. All the flags are listed as of type
|
||||
// string, since we only need to parse them and pass them on to the standard
|
||||
// go flag package. The drawback of this is the boolean flags need to be
|
||||
// specified with a value, e.g. --test.v=true, instead of just --test.v.
|
||||
// This is also needed since the go-flags package does not accept a value
|
||||
// for boolean arguments (i.e. --test.v=true is illegal), while the
|
||||
// standard go flag package does, making calls by go test (which uses values
|
||||
// for boolean arguments) illegal when passed through go-flags.
|
||||
type testOptions struct {
|
||||
// Report as tests are run; default is silent for success.
|
||||
Short string `long:"test.short" default:"false" description:"run smaller test suite to save time"`
|
||||
OutputDir string `long:"test.outputdir" default:"" description:"directory in which to write profiles"`
|
||||
Chatty string `long:"test.v" default:"false" description:"verbose: print additional output"`
|
||||
Count string `long:"test.count" default:"1" description:"run tests and benchmarks n times"`
|
||||
CoverProfile string `long:"test.coverprofile" default:"" description:"write a coverage profile to the named file after execution"`
|
||||
Match string `long:"test.run" default:"" description:"regular expression to select tests and examples to run"`
|
||||
MemProfile string `long:"test.memprofile" default:"" description:"write a memory profile to the named file after execution"`
|
||||
MemProfileRate string `long:"test.memprofilerate" default:"0" description:"if >=0, sets runtime.MemProfileRate"`
|
||||
CPUProfile string `long:"test.cpuprofile" default:"" description:"write a cpu profile to the named file during execution"`
|
||||
BlockProfile string `long:"test.blockprofile" default:"" description:"write a goroutine blocking profile to the named file after execution"`
|
||||
BlockProfileRate string `long:"test.blockprofilerate" default:"1" description:"if >= 0, calls runtime.SetBlockProfileRate()"`
|
||||
TraceFile string `long:"test.trace" default:"" description:"write an execution trace to the named file after execution"`
|
||||
Timeout string `long:"test.timeout" default:"0" description:"if positive, sets an aggregate time limit for all tests"`
|
||||
CPUListStr string `long:"test.cpu" default:"" description:"comma-separated list of number of CPUs to use for each test"`
|
||||
Parallel string `long:"test.parallel" description:"maximum test parallelism"`
|
||||
|
||||
MatchBenchmarks string `long:"test.bench" default:"" description:"regular expression per path component to select benchmarks to run"`
|
||||
BenchTime string `long:"test.benchtime" description:"approximate run time for each benchmark"`
|
||||
BenchmarkMemory string `long:"test.benchmem" default:"false" description:"print memory allocations for benchmarks"`
|
||||
|
||||
SystemTest string `long:"systemTest" default:"false" description:"run system test"`
|
||||
}
|
||||
|
||||
var testOpts testOptions
|
||||
var testGroup *flags.Group
|
||||
|
||||
func init() {
|
||||
systemTest = flag.Bool("systemTest", false, "Run system test")
|
||||
}
|
||||
|
||||
// addDash adds an extra dash to long options
|
||||
// specified with a single dash, e.g. addDash("-test.v")
|
||||
// returns "--test.v". Other strings are
|
||||
// returned unchanged.
|
||||
func addDash(s string) string {
|
||||
if len(s) > 2 && s[0] == '-' && s[1] != '-' {
|
||||
return "-" + s
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// make sure all single dash arguments
|
||||
// are converted to using double dashes
|
||||
// to make them parseable by the go-flags
|
||||
// package
|
||||
for i := range os.Args {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if os.Args[i] == "--" {
|
||||
break
|
||||
}
|
||||
|
||||
os.Args[i] = addDash(os.Args[i])
|
||||
}
|
||||
|
||||
// create a new parser to parse just the test options
|
||||
testParser := flags.NewParser(nil, flags.IgnoreUnknown|flags.Default)
|
||||
testGroup, _ = testParser.AddGroup("go test Options", "go test Options", &testOpts)
|
||||
_, err := testParser.Parse()
|
||||
if err != nil {
|
||||
if err := err.(*flags.Error); err != nil && err.Type == flags.ErrHelp {
|
||||
// do not exit if --systemTest == "true", so that we
|
||||
// can output the help for the port-layer-server test
|
||||
// binary
|
||||
o := testGroup.FindOptionByLongName("systemTest")
|
||||
if !o.IsSet() || o.Value().(string) != "true" {
|
||||
os.Exit(0)
|
||||
}
|
||||
} else {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// build up arguments to pass to the standard
|
||||
// go flag package
|
||||
for _, o := range testGroup.Options() {
|
||||
if !o.IsSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
flagArgs = append(flagArgs, fmt.Sprintf("-%s=%s", o.LongName, o.Value()))
|
||||
}
|
||||
|
||||
flag.CommandLine.Parse(flagArgs)
|
||||
|
||||
// execute tests
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestSystem(t *testing.T) {
|
||||
if *systemTest {
|
||||
// add the test options to the default parser
|
||||
parser.AddGroup("go test Options", "go test Options", &testOpts)
|
||||
|
||||
main()
|
||||
}
|
||||
}
|
||||
98
vendor/github.com/vmware/vic/cmd/rpctool/main.go
generated
vendored
Normal file
98
vendor/github.com/vmware/vic/cmd/rpctool/main.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vmw-guestinfo/rpcout"
|
||||
"github.com/vmware/vmw-guestinfo/rpcvmx"
|
||||
"github.com/vmware/vmw-guestinfo/vmcheck"
|
||||
)
|
||||
|
||||
var (
|
||||
set bool
|
||||
get bool
|
||||
fork bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
trace.Logger.Level = log.DebugLevel
|
||||
|
||||
flag.BoolVar(&set, "set", false, "Sets the guestinfo.KEY with the string VALUE")
|
||||
flag.BoolVar(&get, "get", false, "Returns the config string in the guestinfo.* namespace")
|
||||
flag.BoolVar(&fork, "fork", false, "VMFork")
|
||||
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
if version.Show() {
|
||||
// #nosec: Errors unhandled.
|
||||
fmt.Fprintf(os.Stdout, "%s\n", version.String())
|
||||
return
|
||||
}
|
||||
|
||||
isVM, err := vmcheck.IsVirtualWorld()
|
||||
if err != nil {
|
||||
log.Fatalf("Error: %s", err)
|
||||
}
|
||||
|
||||
if !isVM {
|
||||
log.Fatalf("ERROR: not in a virtual world.")
|
||||
}
|
||||
|
||||
if !set && !get && !fork {
|
||||
flag.Usage()
|
||||
}
|
||||
|
||||
config := rpcvmx.NewConfig()
|
||||
if set {
|
||||
if flag.NArg() != 2 {
|
||||
log.Fatalf("ERROR: Please provide guestinfo key / value pair (eg; -set foo bar")
|
||||
}
|
||||
if err := config.SetString(flag.Arg(0), flag.Arg(1)); err != nil {
|
||||
log.Fatalf("ERROR: SetString failed with %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if get {
|
||||
if flag.NArg() != 1 {
|
||||
log.Fatalf("ERROR: Please provide guestinfo key (eg; -get foo)")
|
||||
}
|
||||
if out, err := config.String(flag.Arg(0), ""); err != nil {
|
||||
log.Fatalf("ERROR: String failed with %s", err)
|
||||
} else {
|
||||
fmt.Printf("%s\n", out)
|
||||
}
|
||||
}
|
||||
|
||||
if fork {
|
||||
out, ok, err := rpcout.SendOne("vmfork-begin -1 -1")
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s | %s | %t", err, out, ok)
|
||||
} else if !ok {
|
||||
log.Fatalf("FAILED: %s", out)
|
||||
} else {
|
||||
fmt.Printf("%s\n", out)
|
||||
}
|
||||
}
|
||||
}
|
||||
723
vendor/github.com/vmware/vic/cmd/tether/attach.go
generated
vendored
Normal file
723
vendor/github.com/vmware/vic/cmd/tether/attach.go
generated
vendored
Normal file
@@ -0,0 +1,723 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/vmware/vic/lib/migration/feature"
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
"github.com/vmware/vic/lib/tether/msgs"
|
||||
"github.com/vmware/vic/pkg/serial"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
attachChannelType = "attach"
|
||||
)
|
||||
|
||||
// server is the singleton attachServer for the tether - there can be only one
|
||||
// as the backchannel line protocol may not provide multiplexing of connections
|
||||
var server AttachServer
|
||||
var once sync.Once
|
||||
|
||||
type AttachServer interface {
|
||||
tether.Extension
|
||||
|
||||
start() error
|
||||
stop() error
|
||||
}
|
||||
|
||||
// config is a struct that holds Sessions and Execs
|
||||
type config struct {
|
||||
Key []byte
|
||||
|
||||
Sessions map[string]*tether.SessionConfig
|
||||
Execs map[string]*tether.SessionConfig
|
||||
}
|
||||
|
||||
type attachServerSSH struct {
|
||||
// serializes data access for exported functions
|
||||
m sync.Mutex
|
||||
|
||||
// conn is the underlying net.Conn which carries SSH
|
||||
// held directly as it is how we stop the attach server
|
||||
conn struct {
|
||||
sync.Mutex
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
// we pass serverConn to the channelMux goroutine so we need to lock it
|
||||
serverConn struct {
|
||||
sync.Mutex
|
||||
*ssh.ServerConn
|
||||
}
|
||||
|
||||
// extension local copy of the bits of config important to attach
|
||||
config config
|
||||
sshConfig *ssh.ServerConfig
|
||||
|
||||
enabled int32
|
||||
|
||||
// Cancelable context and its cancel func. Used for resolving the deadlock
|
||||
// between run() and stop()
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// INTERNAL: must set by testAttachServer only
|
||||
testing bool
|
||||
}
|
||||
|
||||
// NewAttachServerSSH either creates a new instance or returns the initialized one
|
||||
func NewAttachServerSSH() AttachServer {
|
||||
once.Do(func() {
|
||||
// create a cancelable context and assign it to the CancelFunc
|
||||
// it isused for resolving the deadlock between run() and stop()
|
||||
// it has a Background parent as we don't want timeouts here,
|
||||
// otherwise we may start leaking goroutines in the handshake code
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
server = &attachServerSSH{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
})
|
||||
return server
|
||||
}
|
||||
|
||||
// Reload - tether.Extension implementation
|
||||
func (t *attachServerSSH) Reload(tconfig *tether.ExecutorConfig) error {
|
||||
defer trace.End(trace.Begin("attach reload"))
|
||||
|
||||
t.m.Lock()
|
||||
defer t.m.Unlock()
|
||||
|
||||
// We copy this stuff so that we're not referencing the direct config
|
||||
// structure if/while it's being updated.
|
||||
// The subelements generally have locks or updated in single assignment
|
||||
t.config.Key = tconfig.Key
|
||||
t.config.Sessions = make(map[string]*tether.SessionConfig)
|
||||
for k, v := range tconfig.Sessions {
|
||||
t.config.Sessions[k] = v
|
||||
}
|
||||
|
||||
t.config.Execs = make(map[string]*tether.SessionConfig)
|
||||
for k, v := range tconfig.Execs {
|
||||
t.config.Execs[k] = v
|
||||
}
|
||||
|
||||
err := server.start()
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("unable to start attach server: %s", err)
|
||||
log.Error(detail)
|
||||
return errors.New(detail)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enable sets the enabled to true
|
||||
func (t *attachServerSSH) Enable() {
|
||||
atomic.StoreInt32(&t.enabled, 1)
|
||||
}
|
||||
|
||||
// Disable sets the enabled to false
|
||||
func (t *attachServerSSH) Disable() {
|
||||
atomic.StoreInt32(&t.enabled, 0)
|
||||
}
|
||||
|
||||
// Enabled returns whether the enabled is true
|
||||
func (t *attachServerSSH) Enabled() bool {
|
||||
return atomic.LoadInt32(&t.enabled) == 1
|
||||
}
|
||||
|
||||
func (t *attachServerSSH) Start() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop needed for tether.Extensions interface
|
||||
func (t *attachServerSSH) Stop() error {
|
||||
defer trace.End(trace.Begin("stop attach server"))
|
||||
|
||||
t.m.Lock()
|
||||
defer t.m.Unlock()
|
||||
|
||||
// calling server.start not t.start so that test impl gets invoked
|
||||
return server.stop()
|
||||
}
|
||||
|
||||
func (t *attachServerSSH) reload() error {
|
||||
t.serverConn.Lock()
|
||||
defer t.serverConn.Unlock()
|
||||
|
||||
// push the exec'ed session ids to the portlayer
|
||||
if t.serverConn.ServerConn != nil {
|
||||
msg := msgs.ContainersMsg{
|
||||
IDs: t.sessions(false),
|
||||
}
|
||||
payload := msg.Marshal()
|
||||
|
||||
ok, _, err := t.serverConn.SendRequest(msgs.ContainersReq, true, payload)
|
||||
if !ok || err != nil {
|
||||
return fmt.Errorf("failed to send container ids: %s, %t", err, ok)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *attachServerSSH) start() error {
|
||||
defer trace.End(trace.Begin("start attach server"))
|
||||
|
||||
// if we come here while enabled, reload
|
||||
if t.Enabled() {
|
||||
log.Debugf("Start called while enabled, reloading")
|
||||
if err := t.reload(); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// don't assume that the key hasn't changed
|
||||
pkey, err := ssh.ParsePrivateKey([]byte(t.config.Key))
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("failed to load key for attach: %s", err)
|
||||
log.Error(detail)
|
||||
return errors.New(detail)
|
||||
}
|
||||
|
||||
// An SSH server is represented by a ServerConfig, which holds
|
||||
// certificate details and handles authentication of ServerConns.
|
||||
// TODO: update this with generated credentials for the appliance
|
||||
t.sshConfig = &ssh.ServerConfig{
|
||||
PublicKeyCallback: func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
if c.User() == "daemon" {
|
||||
return &ssh.Permissions{}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("expected daemon user")
|
||||
},
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
if c.User() == "daemon" {
|
||||
return &ssh.Permissions{}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("expected daemon user")
|
||||
},
|
||||
NoClientAuth: true,
|
||||
}
|
||||
t.sshConfig.AddHostKey(pkey)
|
||||
|
||||
// enable the server and start it
|
||||
t.Enable()
|
||||
go t.run()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// stop is not thread safe with start
|
||||
func (t *attachServerSSH) stop() error {
|
||||
defer trace.End(trace.Begin("stop attach server"))
|
||||
|
||||
if t == nil {
|
||||
err := fmt.Errorf("attach server is not configured")
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !t.Enabled() {
|
||||
err := fmt.Errorf("attach server is not enabled")
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// disable the server
|
||||
t.Disable()
|
||||
|
||||
// This context is used by backchannel only. We need to cancel it before
|
||||
// trying to obtain the following lock so that backchannel interrupts the
|
||||
// underlying Read call by calling Close on it.
|
||||
// The lock is held by backchannel's caller and not released until it returns
|
||||
log.Debugf("Canceling AttachServer's context")
|
||||
t.cancel()
|
||||
|
||||
t.conn.Lock()
|
||||
if t.conn.conn != nil {
|
||||
log.Debugf("Close called again on rawconn - squashing")
|
||||
// #nosec: Errors unhandled.
|
||||
t.conn.conn.Close()
|
||||
t.conn.conn = nil
|
||||
}
|
||||
t.conn.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func backchannel(ctx context.Context, conn net.Conn) error {
|
||||
defer trace.End(trace.Begin("establish tether backchannel"))
|
||||
|
||||
// used for shutting down the goroutine cleanly otherwise we leak a goroutine for every successful return from this function
|
||||
done := make(chan struct{})
|
||||
|
||||
// HACK: currently RawConn dosn't implement timeout so throttle the spinning
|
||||
// it does implement the Timeout methods so the intermediary code can be written
|
||||
// to support it, but they are stub implementation in rawconn impl.
|
||||
|
||||
// This needs to tick *faster* than the ticker in connection.go on the
|
||||
// portlayer side. The PL sends the first syn and if this isn't waiting,
|
||||
// alignment will take a few rounds (or it may never happen).
|
||||
ticker := time.NewTicker(10 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
// We run this in a separate goroutine because HandshakeServer
|
||||
// calls a Read on rawconn which is a blocking call which causes
|
||||
// the caller to block as well so this is the only way to cancel.
|
||||
// Calling Close() will unblock us and on the next tick we will
|
||||
// return ctx.Err()
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
conn.Close()
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
deadline, ok := ctx.Deadline()
|
||||
if ok {
|
||||
conn.SetReadDeadline(deadline)
|
||||
}
|
||||
|
||||
err := serial.HandshakeServer(conn)
|
||||
if err == nil {
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
close(done)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch et := err.(type) {
|
||||
case *serial.HandshakeError:
|
||||
log.Debugf("HandshakeServer: %v", et)
|
||||
default:
|
||||
log.Errorf("HandshakeServer: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *attachServerSSH) establish() error {
|
||||
var err error
|
||||
|
||||
// we hold the t.conn.Lock during the scope of this function
|
||||
t.conn.Lock()
|
||||
defer t.conn.Unlock()
|
||||
|
||||
// tests are passing their own connections so do not create connections when testing is set
|
||||
if !t.testing {
|
||||
// close the connection if required
|
||||
if t.conn.conn != nil {
|
||||
// #nosec: Errors unhandled.
|
||||
t.conn.conn.Close()
|
||||
t.conn.conn = nil
|
||||
}
|
||||
t.conn.conn, err = rawConnectionFromSerial()
|
||||
if err != nil {
|
||||
detail := fmt.Errorf("failed to create raw connection: %s", err)
|
||||
log.Error(detail)
|
||||
return detail
|
||||
}
|
||||
} else {
|
||||
// A series of unfortunate events can lead calling backchannel with nil when we run unit tests.
|
||||
// https://github.com/vmware/vic/pull/5327#issuecomment-305619860
|
||||
// This check is here to handle that
|
||||
if t.conn.conn == nil {
|
||||
return fmt.Errorf("nil connection")
|
||||
}
|
||||
}
|
||||
|
||||
// wait for backchannel to establish
|
||||
err = backchannel(t.ctx, t.conn.conn)
|
||||
if err != nil {
|
||||
detail := fmt.Errorf("failed to establish backchannel: %s", err)
|
||||
log.Error(detail)
|
||||
return detail
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *attachServerSSH) cleanup() {
|
||||
t.serverConn.Lock()
|
||||
defer t.serverConn.Unlock()
|
||||
|
||||
log.Debugf("cleanup on connection")
|
||||
|
||||
if t.serverConn.ServerConn != nil {
|
||||
log.Debugf("closing underlying connection")
|
||||
t.serverConn.Close()
|
||||
t.serverConn.ServerConn = nil
|
||||
}
|
||||
}
|
||||
|
||||
// run should not be called directly, but via start
|
||||
// run will establish an ssh server listening on the backchannel
|
||||
func (t *attachServerSSH) run() error {
|
||||
defer trace.End(trace.Begin("main attach server loop"))
|
||||
|
||||
var established bool
|
||||
|
||||
var chans <-chan ssh.NewChannel
|
||||
var reqs <-chan *ssh.Request
|
||||
var err error
|
||||
|
||||
// main run loop
|
||||
for t.Enabled() {
|
||||
t.serverConn.Lock()
|
||||
established = t.serverConn.ServerConn != nil
|
||||
t.serverConn.Unlock()
|
||||
|
||||
// keep waiting for the connection to establish
|
||||
for !established && t.Enabled() {
|
||||
log.Infof("Trying to establish a connection")
|
||||
|
||||
if err := t.establish(); err != nil {
|
||||
log.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// create the SSH server using underlying t.conn
|
||||
t.serverConn.Lock()
|
||||
|
||||
t.serverConn.ServerConn, chans, reqs, err = ssh.NewServerConn(t.conn.conn, t.sshConfig)
|
||||
if err != nil {
|
||||
detail := fmt.Errorf("failed to establish ssh handshake: %s", err)
|
||||
log.Error(detail)
|
||||
}
|
||||
established = t.serverConn.ServerConn != nil
|
||||
|
||||
t.serverConn.Unlock()
|
||||
}
|
||||
|
||||
// Global requests
|
||||
go t.globalMux(reqs, t.cleanup)
|
||||
|
||||
log.Infof("Ready to service attach requests")
|
||||
// Service the incoming channels
|
||||
for attachchan := range chans {
|
||||
// The only channel type we'll support is attach
|
||||
if attachchan.ChannelType() != attachChannelType {
|
||||
detail := fmt.Sprintf("unknown channel type %s", attachchan.ChannelType())
|
||||
attachchan.Reject(ssh.UnknownChannelType, detail)
|
||||
log.Error(detail)
|
||||
continue
|
||||
}
|
||||
|
||||
// check we have a Session matching the requested ID
|
||||
bytes := attachchan.ExtraData()
|
||||
if bytes == nil {
|
||||
detail := "attach channel requires ID in ExtraData"
|
||||
attachchan.Reject(ssh.Prohibited, detail)
|
||||
log.Error(detail)
|
||||
continue
|
||||
}
|
||||
|
||||
sessionid := string(bytes)
|
||||
|
||||
s, oks := t.config.Sessions[sessionid]
|
||||
e, oke := t.config.Execs[sessionid]
|
||||
if !oks && !oke {
|
||||
detail := fmt.Sprintf("session %s is invalid", sessionid)
|
||||
attachchan.Reject(ssh.Prohibited, detail)
|
||||
log.Error(detail)
|
||||
continue
|
||||
}
|
||||
|
||||
// we have sessionid
|
||||
session := s
|
||||
if oke {
|
||||
session = e
|
||||
}
|
||||
|
||||
// session is potentially blocked in launch until we've got the unblock message, so we cannot lock it.
|
||||
// check that session is valid
|
||||
// The detail remains concise as it'll eventually make its way to the user
|
||||
if session.Started != "" && session.Started != "true" {
|
||||
detail := fmt.Sprintf("launch failed with: %s", session.Started)
|
||||
attachchan.Reject(ssh.Prohibited, detail)
|
||||
log.Error(detail)
|
||||
continue
|
||||
}
|
||||
|
||||
if session.StopTime != 0 {
|
||||
detail := fmt.Sprintf("process finished with exit code: %d", session.ExitStatus)
|
||||
attachchan.Reject(ssh.Prohibited, detail)
|
||||
log.Error(detail)
|
||||
continue
|
||||
}
|
||||
|
||||
channel, requests, err := attachchan.Accept()
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("could not accept channel: %s", err)
|
||||
log.Errorf(detail)
|
||||
continue
|
||||
}
|
||||
|
||||
// bind the channel to the Session
|
||||
log.Debugf("binding reader/writers for channel for %s", sessionid)
|
||||
|
||||
log.Debugf("Adding [%p] to Outwriter", channel)
|
||||
session.Outwriter.Add(channel)
|
||||
log.Debugf("Adding [%p] to Reader", channel)
|
||||
session.Reader.Add(channel)
|
||||
|
||||
// cleanup on detach from the session
|
||||
cleanup := func() {
|
||||
log.Debugf("Cleanup on detach from the session")
|
||||
|
||||
log.Debugf("Removing [%p] from Outwriter", channel)
|
||||
session.Outwriter.Remove(channel)
|
||||
|
||||
log.Debugf("Removing [%p] from Reader", channel)
|
||||
session.Reader.Remove(channel)
|
||||
|
||||
channel.Close()
|
||||
}
|
||||
|
||||
detach := cleanup
|
||||
// tty's merge stdout and stderr so we don't bind an additional reader in that case but we need to do so for non-tty
|
||||
if !session.Tty {
|
||||
// persist the value as we end up with different values each time we access it
|
||||
stderr := channel.Stderr()
|
||||
|
||||
log.Debugf("Adding [%p] to Errwriter", stderr)
|
||||
session.Errwriter.Add(stderr)
|
||||
|
||||
detach = func() {
|
||||
log.Debugf("Cleanup on detach from the session (non-tty)")
|
||||
|
||||
log.Debugf("Removing [%p] from Errwriter", stderr)
|
||||
session.Errwriter.Remove(stderr)
|
||||
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
log.Debugf("reader/writers bound for channel for %s", sessionid)
|
||||
|
||||
go t.channelMux(requests, session, detach)
|
||||
}
|
||||
log.Info("Incoming attach channel closed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *attachServerSSH) sessions(all bool) []string {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var keys []string
|
||||
|
||||
// this iterates the local copies of the sessions maps
|
||||
// so we don't need to care whether they're initialized or not
|
||||
// as extension reload comes after that point
|
||||
|
||||
// whether include sessions or not
|
||||
if all {
|
||||
for k, v := range t.config.Sessions {
|
||||
if v.Active && v.StopTime == 0 {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range t.config.Execs {
|
||||
// skip those that have had launch errors
|
||||
if v.Active && v.StopTime == 0 && (v.Started == "" || v.Started == "true") {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Returning %d keys", len(keys))
|
||||
return keys
|
||||
}
|
||||
|
||||
func (t *attachServerSSH) globalMux(in <-chan *ssh.Request, cleanup func()) {
|
||||
defer trace.End(trace.Begin("attach server global request handler"))
|
||||
|
||||
// cleanup function passed by the caller
|
||||
defer cleanup()
|
||||
|
||||
// for the actions after we process the request
|
||||
var pendingFn func()
|
||||
for req := range in {
|
||||
var payload []byte
|
||||
ok := true
|
||||
|
||||
log.Infof("received global request type %v", req.Type)
|
||||
|
||||
switch req.Type {
|
||||
case msgs.ContainersReq:
|
||||
msg := msgs.ContainersMsg{
|
||||
IDs: t.sessions(true),
|
||||
}
|
||||
payload = msg.Marshal()
|
||||
case msgs.VersionReq:
|
||||
msg := msgs.VersionMsg{
|
||||
Version: feature.MaxPluginVersion - 1,
|
||||
}
|
||||
payload = msg.Marshal()
|
||||
default:
|
||||
ok = false
|
||||
payload = []byte("unknown global request type: " + req.Type)
|
||||
}
|
||||
|
||||
log.Debugf("Returning payload: %s", string(payload))
|
||||
|
||||
// make sure that errors get send back if we failed
|
||||
if req.WantReply {
|
||||
log.Debugf("Sending global request reply %t back with %#v", ok, payload)
|
||||
if err := req.Reply(ok, payload); err != nil {
|
||||
log.Warnf("Failed to reply a global request back")
|
||||
}
|
||||
}
|
||||
|
||||
// run any pending work now that a reply has been sent
|
||||
if pendingFn != nil {
|
||||
log.Debug("Invoking pending work for global mux")
|
||||
go pendingFn()
|
||||
pendingFn = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *attachServerSSH) channelMux(in <-chan *ssh.Request, session *tether.SessionConfig, cleanup func()) {
|
||||
defer trace.End(trace.Begin("attach server channel request handler"))
|
||||
|
||||
// cleanup function passed by the caller
|
||||
defer cleanup()
|
||||
|
||||
// to make sure we close the channel once
|
||||
var once sync.Once
|
||||
|
||||
// for the actions after we process the request
|
||||
var pendingFn func()
|
||||
for req := range in {
|
||||
ok := true
|
||||
abort := false
|
||||
|
||||
log.Infof("received channel mux type %v", req.Type)
|
||||
|
||||
switch req.Type {
|
||||
case msgs.PingReq:
|
||||
log.Infof("Received PingReq for %s", session.ID)
|
||||
|
||||
if string(req.Payload) != msgs.PingMsg {
|
||||
log.Infof("Received corrupted PingReq for %s", session.ID)
|
||||
ok = false
|
||||
}
|
||||
case msgs.UnblockReq:
|
||||
log.Infof("Received UnblockReq for %s", session.ID)
|
||||
|
||||
if string(req.Payload) != msgs.UnblockMsg {
|
||||
log.Infof("Received corrupted UnblockReq for %s", session.ID)
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
|
||||
// if the process has exited, or couldn't launch
|
||||
if session.Started != "" && session.Started != "true" {
|
||||
// we need to force the session closed so that error handling occurs on the callers
|
||||
// side
|
||||
ok = false
|
||||
abort = true
|
||||
} else {
|
||||
// unblock ^ (above)
|
||||
pendingFn = func() {
|
||||
once.Do(func() {
|
||||
launchChan := session.ClearToLaunch
|
||||
if session.RunBlock && launchChan != nil && session.Started == "" {
|
||||
log.Infof("Unblocking the launch of %s", session.Common.ID)
|
||||
// make sure that portlayer received the container id back
|
||||
launchChan <- struct{}{}
|
||||
log.Infof("Unblocked the launch of %s", session.Common.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
case msgs.WindowChangeReq:
|
||||
session.Lock()
|
||||
pty := session.Pty
|
||||
session.Unlock()
|
||||
|
||||
msg := msgs.WindowChangeMsg{}
|
||||
if pty == nil {
|
||||
ok = false
|
||||
log.Errorf("illegal window-change request for non-tty")
|
||||
} else if err := msg.Unmarshal(req.Payload); err != nil {
|
||||
ok = false
|
||||
log.Errorf(err.Error())
|
||||
} else if err := resizePty(pty.Fd(), &msg); err != nil {
|
||||
ok = false
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
case msgs.CloseStdinReq:
|
||||
log.Infof("Received CloseStdinReq for %s", session.ID)
|
||||
|
||||
log.Debugf("Configuring reader to propagate EOF for %s", session.ID)
|
||||
session.Reader.PropagateEOF(true)
|
||||
default:
|
||||
ok = false
|
||||
log.Error(fmt.Sprintf("ssh request type %s is not supported", req.Type))
|
||||
}
|
||||
|
||||
// payload is ignored on channel specific replies. The ok is passed, however.
|
||||
if req.WantReply {
|
||||
log.Debugf("Sending channel request reply %t back", ok)
|
||||
if err := req.Reply(ok, nil); err != nil {
|
||||
log.Warnf("Failed replying to a channel request: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// run any pending work now that a reply has been sent
|
||||
if pendingFn != nil {
|
||||
log.Debug("Invoking pending work for channel mux")
|
||||
go pendingFn()
|
||||
pendingFn = nil
|
||||
}
|
||||
|
||||
if abort {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The syscall struct
|
||||
type winsize struct {
|
||||
wsRow uint16
|
||||
wsCol uint16
|
||||
wsXpixel uint16
|
||||
wsYpixel uint16
|
||||
}
|
||||
121
vendor/github.com/vmware/vic/cmd/tether/attach_linux.go
generated
vendored
Normal file
121
vendor/github.com/vmware/vic/cmd/tether/attach_linux.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/tether/msgs"
|
||||
"github.com/vmware/vic/pkg/serial"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
var backchannelMode = os.ModePerm
|
||||
|
||||
func setTerminalSpeed(fd uintptr) error {
|
||||
var current struct {
|
||||
termios syscall.Termios
|
||||
}
|
||||
|
||||
// get the current state
|
||||
// #nosec: Use of unsafe calls should be audited
|
||||
if _, _, errno := syscall.Syscall6(syscall.SYS_IOCTL,
|
||||
uintptr(fd),
|
||||
syscall.TCGETS,
|
||||
uintptr(unsafe.Pointer(¤t.termios)),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
|
||||
// copy it as the future
|
||||
future := current.termios
|
||||
|
||||
// unset 9600 bps
|
||||
future.Cflag &^= syscall.B9600
|
||||
// set them to 115200 bps
|
||||
future.Cflag |= syscall.B115200
|
||||
future.Ispeed = syscall.B115200
|
||||
future.Ospeed = syscall.B115200
|
||||
|
||||
// set the future values
|
||||
// #nosec: Use of unsafe calls should be audited
|
||||
if _, _, errno := syscall.Syscall6(
|
||||
syscall.SYS_IOCTL,
|
||||
uintptr(fd),
|
||||
syscall.TCSETS,
|
||||
uintptr(unsafe.Pointer(&future)),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rawConnectionFromSerial() (net.Conn, error) {
|
||||
log.Info("opening ttyS0 for backchannel")
|
||||
f, err := os.OpenFile(path.Join(pathPrefix, "ttyS0"), os.O_RDWR|os.O_SYNC|syscall.O_NOCTTY, backchannelMode)
|
||||
if err != nil {
|
||||
detail := fmt.Errorf("failed to open serial port for backchannel: %s", err)
|
||||
log.Error(detail)
|
||||
return nil, detail
|
||||
}
|
||||
|
||||
// set the provided FDs to raw if it's a termial
|
||||
// 0 is the uninitialized value for Fd
|
||||
if f.Fd() != 0 && terminal.IsTerminal(int(f.Fd())) {
|
||||
log.Info("setting terminal to raw mode")
|
||||
_, err := terminal.MakeRaw(int(f.Fd()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := setTerminalSpeed(f.Fd()); err != nil {
|
||||
log.Errorf("Setting terminal speed failed with %s", err)
|
||||
}
|
||||
|
||||
log.Infof("creating raw connection from ttyS0 (fd=%d)", f.Fd())
|
||||
return serial.NewFileConn(f)
|
||||
}
|
||||
|
||||
func resizePty(pty uintptr, winSize *msgs.WindowChangeMsg) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
ws := &winsize{uint16(winSize.Rows), uint16(winSize.Columns), uint16(winSize.WidthPx), uint16(winSize.HeightPx)}
|
||||
// #nosec: Use of unsafe calls should be audited
|
||||
_, _, errno := syscall.Syscall(
|
||||
syscall.SYS_IOCTL,
|
||||
pty,
|
||||
syscall.TIOCSWINSZ,
|
||||
uintptr(unsafe.Pointer(ws)),
|
||||
)
|
||||
if errno != 0 {
|
||||
return syscall.Errno(errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1096
vendor/github.com/vmware/vic/cmd/tether/attach_test.go
generated
vendored
Normal file
1096
vendor/github.com/vmware/vic/cmd/tether/attach_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
74
vendor/github.com/vmware/vic/cmd/tether/haveged_linux.go
generated
vendored
Normal file
74
vendor/github.com/vmware/vic/cmd/tether/haveged_linux.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
)
|
||||
|
||||
// Haveged is a tether extension that wraps command
|
||||
type Haveged struct {
|
||||
p *os.Process
|
||||
exec func() (*os.Process, error)
|
||||
}
|
||||
|
||||
// NewHaveged returns a tether.Extension that wraps haveged
|
||||
func NewHaveged() *Haveged {
|
||||
return &Haveged{
|
||||
exec: func() (*os.Process, error) {
|
||||
args := []string{"/.tether/lib/ld-linux-x86-64.so.2", "--library-path", "/.tether/lib", "/.tether/haveged", "-w", "1024", "-v", "1", "-F"}
|
||||
// #nosec: Subprocess launching with variable
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
|
||||
log.Infof("Starting haveged with args: %q", args)
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Errorf("Starting haveged failed with %q", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
return cmd.Process, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start implementation of the tether.Extension interface
|
||||
func (h *Haveged) Start() error {
|
||||
log.Infof("Starting haveged")
|
||||
|
||||
var err error
|
||||
h.p, err = h.exec()
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop implementation of the tether.Extension interface
|
||||
func (h *Haveged) Stop() error {
|
||||
log.Infof("Stopping haveged")
|
||||
|
||||
if h.p != nil {
|
||||
return h.p.Kill()
|
||||
}
|
||||
return fmt.Errorf("haveged process is missing")
|
||||
}
|
||||
|
||||
// Reload implementation of the tether.Extension interface
|
||||
func (h *Haveged) Reload(config *tether.ExecutorConfig) error {
|
||||
// haveged doesn't support reloading
|
||||
return nil
|
||||
}
|
||||
139
vendor/github.com/vmware/vic/cmd/tether/main_linux.go
generated
vendored
Normal file
139
vendor/github.com/vmware/vic/cmd/tether/main_linux.go
generated
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
viclog "github.com/vmware/vic/pkg/log"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
var tthr tether.Tether
|
||||
|
||||
func init() {
|
||||
// Initiliaze logger with default TextFormatter
|
||||
log.SetFormatter(viclog.NewTextFormatter())
|
||||
|
||||
// use the same logger for trace and other logging
|
||||
trace.Logger.Level = log.DebugLevel
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
// init and start the HUP handler
|
||||
startSignalHandler()
|
||||
|
||||
pathPrefix = "/dev"
|
||||
}
|
||||
|
||||
func main() {
|
||||
if strings.HasSuffix(os.Args[0], "-debug") {
|
||||
// very, very verbose
|
||||
extraconfig.SetLogLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Errorf("run time panic: %s : %s", r, debug.Stack())
|
||||
}
|
||||
halt()
|
||||
}()
|
||||
|
||||
logFile, err := os.OpenFile(path.Join(pathPrefix, "ttyS1"), os.O_WRONLY|os.O_SYNC, 0)
|
||||
if err != nil {
|
||||
log.Errorf("Could not open serial port for debugging info. Some debug info may be lost! Error reported was %s", err)
|
||||
}
|
||||
|
||||
if err = syscall.Dup3(int(logFile.Fd()), int(os.Stderr.Fd()), 0); err != nil {
|
||||
log.Errorf("Could not pipe logfile to standard error due to error %s", err)
|
||||
}
|
||||
|
||||
if _, err = os.Stderr.WriteString("all stderr redirected to debug log"); err != nil {
|
||||
log.Errorf("Could not write to Stderr due to error %s", err)
|
||||
}
|
||||
|
||||
// where to look for the various devices and files related to tether
|
||||
|
||||
// TODO: hard code executor initialization status reporting via guestinfo here
|
||||
sshserver := NewAttachServerSSH()
|
||||
src, err := extraconfig.GuestInfoSource()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
sink, err := extraconfig.GuestInfoSink()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// create the tether
|
||||
tthr = tether.New(src, sink, &operations{})
|
||||
|
||||
// register the attach extension
|
||||
tthr.Register("Attach", sshserver)
|
||||
|
||||
// register the toolbox extension
|
||||
tthr.Register("Toolbox", tether.NewToolbox().InContainer())
|
||||
|
||||
// register the executor extension
|
||||
tthr.Register("Haveged", NewHaveged())
|
||||
|
||||
err = tthr.Start()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("Clean exit from tether")
|
||||
}
|
||||
|
||||
// exit cleanly shuts down the system
|
||||
func halt() {
|
||||
log.Infof("Powering off the system")
|
||||
if strings.HasSuffix(os.Args[0], "-debug") {
|
||||
log.Info("Squashing power off for debug tether")
|
||||
return
|
||||
}
|
||||
|
||||
syscall.Sync()
|
||||
syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF)
|
||||
}
|
||||
|
||||
func startSignalHandler() {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGHUP)
|
||||
|
||||
go func() {
|
||||
for s := range sigs {
|
||||
switch s {
|
||||
case syscall.SIGHUP:
|
||||
log.Infof("Reloading tether configuration")
|
||||
tthr.Reload()
|
||||
default:
|
||||
log.Infof("%s signal not defined", s.String())
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
160
vendor/github.com/vmware/vic/cmd/tether/main_test.go
generated
vendored
Normal file
160
vendor/github.com/vmware/vic/cmd/tether/main_test.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
func init() {
|
||||
trace.Logger.Level = log.DebugLevel
|
||||
}
|
||||
|
||||
// createFakeDevices creates regular files or pipes in place of the char devices used
|
||||
// in a full VM
|
||||
func createFakeDevices() error {
|
||||
var err error
|
||||
|
||||
// create control channel
|
||||
path := fmt.Sprintf("%s/ttyS0", pathPrefix)
|
||||
err = syscall.Mkfifo(path+"s", uint32(backchannelMode))
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("failed to create fifo pipe %ss for com0: %s", path, err)
|
||||
return errors.New(detail)
|
||||
}
|
||||
err = syscall.Mkfifo(path+"c", uint32(backchannelMode))
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("failed to create fifo pipe %sc for com0: %s", path, err)
|
||||
return errors.New(detail)
|
||||
}
|
||||
log.Debugf("created %s/ttyS0{c,s} as raw conn pipes", pathPrefix)
|
||||
|
||||
// others are non-interactive
|
||||
for i := 1; i < 3; i++ {
|
||||
path = fmt.Sprintf("%s/ttyS%d", pathPrefix, i)
|
||||
_, err = os.Create(path)
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("failed to create %s for com%d: %s", path, i+1, err)
|
||||
return errors.New(detail)
|
||||
}
|
||||
log.Debugf("created %s as persistent log destinations", path)
|
||||
}
|
||||
|
||||
// make an access to urandom
|
||||
path = fmt.Sprintf("%s/urandom", pathPrefix)
|
||||
err = os.Symlink("/dev/urandom", path)
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("failed to create urandom access: %s", err)
|
||||
return errors.New(detail)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testSetup(t *testing.T) *Mocker {
|
||||
var err error
|
||||
|
||||
pathPrefix, err = ioutil.TempDir("", path.Base(t.Name()))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
mocker := tetherTestSetup(t)
|
||||
|
||||
err = os.MkdirAll(pathPrefix, 0777)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
log.Infof("Using %s as test prefix", pathPrefix)
|
||||
|
||||
backchannelMode = os.ModeNamedPipe | os.ModePerm
|
||||
err = createFakeDevices()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
// supply custom attach server so we can inspect its state
|
||||
testServer := &testAttachServer{
|
||||
updated: make(chan bool, 10),
|
||||
attachServerSSH: attachServerSSH{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
},
|
||||
}
|
||||
server = testServer
|
||||
|
||||
return mocker
|
||||
}
|
||||
|
||||
func testTeardown(t *testing.T, mocker *Mocker) {
|
||||
tetherTestTeardown(t, mocker)
|
||||
}
|
||||
|
||||
type mockery struct {
|
||||
reloaded chan bool
|
||||
}
|
||||
|
||||
func (m *mockery) Reload() {
|
||||
m.reloaded <- true
|
||||
}
|
||||
|
||||
func (m *mockery) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockery) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockery) Register(name string, config tether.Extension) {
|
||||
}
|
||||
|
||||
func (m *mockery) LaunchUtility(fn tether.UtilityFn) (<-chan int, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Test reloading via signal helper
|
||||
func TestReload(t *testing.T) {
|
||||
m := &mockery{make(chan bool)}
|
||||
tthr = m
|
||||
|
||||
startSignalHandler()
|
||||
|
||||
if !assert.NoError(t, tether.ReloadConfig()) {
|
||||
return
|
||||
}
|
||||
|
||||
// check the started channel is closed (which gets closed on reconfig)
|
||||
if !assert.True(t, <-m.reloaded) {
|
||||
return
|
||||
}
|
||||
}
|
||||
127
vendor/github.com/vmware/vic/cmd/tether/net_linux_test.go
generated
vendored
Normal file
127
vendor/github.com/vmware/vic/cmd/tether/net_linux_test.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
// addInterface utility method to add an interface to Mocked
|
||||
// This assigns the interface name and returns the "slot" as a string
|
||||
func addInterface(name string, mocker *Mocker) string {
|
||||
mocker.maxSlot++
|
||||
|
||||
mocker.Interfaces[name] = &Interface{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: name,
|
||||
Index: mocker.maxSlot,
|
||||
},
|
||||
Up: true,
|
||||
}
|
||||
|
||||
return strconv.Itoa(mocker.maxSlot)
|
||||
}
|
||||
|
||||
func TestOutboundRuleAndCmd(t *testing.T) {
|
||||
t.Skip("https://github.com/vmware/vic/issues/5965")
|
||||
|
||||
mocker := testSetup(t)
|
||||
defer testTeardown(t, mocker)
|
||||
|
||||
bridge := addInterface("eth1", mocker)
|
||||
|
||||
ip, _ := netlink.ParseIPNet("172.16.0.2/24")
|
||||
gwIP, _ := netlink.ParseIPNet("172.16.0.1/24")
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "outboundrule",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
Diagnostics: executor.Diagnostics{
|
||||
DebugLevel: 3,
|
||||
},
|
||||
Networks: map[string]*executor.NetworkEndpoint{
|
||||
"bridge": {
|
||||
Common: executor.Common{
|
||||
ID: bridge,
|
||||
// interface rename
|
||||
Name: "bridge",
|
||||
},
|
||||
Network: executor.ContainerNetwork{
|
||||
Common: executor.Common{
|
||||
Name: "bridge",
|
||||
},
|
||||
Default: true,
|
||||
Gateway: *gwIP,
|
||||
},
|
||||
Static: true,
|
||||
IP: ip,
|
||||
},
|
||||
},
|
||||
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"outboundrule": {
|
||||
Common: executor.Common{
|
||||
ID: "outboundrule",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: false,
|
||||
Active: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test relative path
|
||||
Path: "./date",
|
||||
Args: []string{"./date", "--reference=/"},
|
||||
Env: []string{"PATH="},
|
||||
Dir: "/bin",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, src, _ := StartTether(t, &cfg, mocker)
|
||||
|
||||
fmt.Println("Waiting for tether start")
|
||||
<-mocker.Started
|
||||
|
||||
// wait for tether to exit
|
||||
fmt.Println("Waiting for tether exit")
|
||||
<-mocker.Cleaned
|
||||
|
||||
result := tether.ExecutorConfig{}
|
||||
extraconfig.Decode(src, &result)
|
||||
|
||||
// confirm outbound rules configured
|
||||
// this should modify state depending on prior rule state
|
||||
|
||||
// confirm command ran - necessary to detect early exit due to net config error
|
||||
// TODO: this should be modifed to fail if the last rule to be configured hasn't completed with expected output
|
||||
// when this is run. Pending mocked iptables interface
|
||||
assert.Equal(t, "true", result.Sessions["outboundrule"].Started, "Expected command to have been started successfully")
|
||||
assert.Equal(t, 0, result.Sessions["outboundrule"].ExitStatus, "Expected command to have exited cleanly")
|
||||
|
||||
}
|
||||
52
vendor/github.com/vmware/vic/cmd/tether/ops.go
generated
vendored
Normal file
52
vendor/github.com/vmware/vic/cmd/tether/ops.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import "github.com/vmware/vic/lib/tether"
|
||||
|
||||
// pathPrefix is present to allow the various files referenced by tether to be placed
|
||||
// in specific directories, primarily for testing.
|
||||
var pathPrefix string
|
||||
|
||||
func (t *operations) Cleanup() error {
|
||||
return t.BaseOperations.Cleanup()
|
||||
}
|
||||
|
||||
func (t *operations) Apply(endpoint *tether.NetworkEndpoint) error {
|
||||
return t.BaseOperations.Apply(endpoint)
|
||||
}
|
||||
|
||||
// HandleSessionExit controls the behaviour on session exit - for the tether if the session exiting
|
||||
// is the primary session (i.e. SessionID matches ExecutorID) then we exit everything.
|
||||
func (t *operations) HandleSessionExit(config *tether.ExecutorConfig, session *tether.SessionConfig) func() {
|
||||
// if the session that's exiting is the primary session, stop the tether
|
||||
return func() {
|
||||
pod := false
|
||||
for _, s := range config.Sessions {
|
||||
if s.ExecutionEnvironment != "" {
|
||||
pod = true
|
||||
}
|
||||
if s.StopTime <= s.StartTime {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, s := range config.Execs {
|
||||
if pod && s.StopTime <= s.StartTime {
|
||||
return
|
||||
}
|
||||
}
|
||||
tthr.Stop()
|
||||
}
|
||||
}
|
||||
490
vendor/github.com/vmware/vic/cmd/tether/ops_linux.go
generated
vendored
Normal file
490
vendor/github.com/vmware/vic/cmd/tether/ops_linux.go
generated
vendored
Normal file
@@ -0,0 +1,490 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/iolog"
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
"github.com/vmware/vic/lib/tether/netfilter"
|
||||
"github.com/vmware/vic/pkg/dio"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
runMountPoint = "/run"
|
||||
|
||||
// default values to set for ulimit fields
|
||||
defaultNOFILE = 1024 * 1024
|
||||
defaultULimit = math.MaxUint64
|
||||
)
|
||||
|
||||
type operations struct {
|
||||
tether.BaseOperations
|
||||
|
||||
logging bool
|
||||
}
|
||||
|
||||
func (t *operations) Log() (io.Writer, error) {
|
||||
defer trace.End(trace.Begin("operations.Log"))
|
||||
|
||||
// redirect logging to the serial log
|
||||
log.Infof("opening %s/ttyS1 for debug log", pathPrefix)
|
||||
f, err := os.OpenFile(path.Join(pathPrefix, "ttyS1"), os.O_RDWR|os.O_SYNC|syscall.O_NOCTTY, 0)
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("failed to open serial port for debug log: %s", err)
|
||||
log.Error(detail)
|
||||
return nil, errors.New(detail)
|
||||
}
|
||||
|
||||
if err := setTerminalSpeed(f.Fd()); err != nil {
|
||||
log.Errorf("Setting terminal speed failed with %s", err)
|
||||
}
|
||||
|
||||
// enable raw mode
|
||||
_, err = terminal.MakeRaw(int(f.Fd()))
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("Making ttyS1 raw failed with %s", err)
|
||||
log.Error(detail)
|
||||
return nil, errors.New(detail)
|
||||
}
|
||||
|
||||
return io.MultiWriter(f, os.Stdout), nil
|
||||
}
|
||||
|
||||
// sessionLogWriter returns a writer that will persist the session output
|
||||
func (t *operations) SessionLog(session *tether.SessionConfig) (dio.DynamicMultiWriter, dio.DynamicMultiWriter, error) {
|
||||
defer trace.End(trace.Begin("configure session log writer"))
|
||||
|
||||
if t.logging {
|
||||
detail := "unable to log more than one session concurrently to persistent logging"
|
||||
log.Warn(detail)
|
||||
// use multi-writer so it's still viable for attach
|
||||
return dio.MultiWriter(), dio.MultiWriter(), nil
|
||||
}
|
||||
|
||||
t.logging = true
|
||||
|
||||
// open SttyS2 for session logging
|
||||
log.Info("opening ttyS2 for session logging")
|
||||
f, err := os.OpenFile(path.Join(pathPrefix, "ttyS2"), os.O_RDWR|os.O_SYNC|syscall.O_NOCTTY, 0)
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("failed to open serial port for session log: %s", err)
|
||||
log.Error(detail)
|
||||
return nil, nil, errors.New(detail)
|
||||
}
|
||||
|
||||
if err := setTerminalSpeed(f.Fd()); err != nil {
|
||||
log.Errorf("Setting terminal speed failed with %s", err)
|
||||
}
|
||||
|
||||
// enable raw mode
|
||||
_, err = terminal.MakeRaw(int(f.Fd()))
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("Making ttyS2 raw failed with %s", err)
|
||||
log.Error(detail)
|
||||
return nil, nil, errors.New(detail)
|
||||
}
|
||||
|
||||
// wrap output in a LogWriter to serialize it into our persisted
|
||||
// containerVM output format, using iolog.LogClock for timestamps
|
||||
lw := iolog.NewLogWriter(f, iolog.LogClock{})
|
||||
|
||||
// use multi-writer so it goes to both screen and session log
|
||||
return dio.MultiWriter(lw, os.Stdout), dio.MultiWriter(lw, os.Stderr), nil
|
||||
}
|
||||
|
||||
func (t *operations) Setup(sink tether.Config) error {
|
||||
if err := t.BaseOperations.Setup(sink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// unmount /run - https://github.com/vmware/vic/issues/1643
|
||||
if err := tether.Sys.Syscall.Unmount(runMountPoint, syscall.MNT_DETACH); err != nil {
|
||||
if errno, ok := err.(syscall.Errno); !ok || errno != syscall.EINVAL {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: ulimit default values should change when we support ulimit configuration
|
||||
ApplyDefaultULimit()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyDefaultULimit sets ulimit fields to their defined default value
|
||||
func ApplyDefaultULimit() {
|
||||
var rLimit syscall.Rlimit
|
||||
|
||||
// NOFILE does not support defaultULimit as a value due to kernel restriction on number of open files
|
||||
rLimit.Max = defaultNOFILE
|
||||
rLimit.Cur = rLimit.Max
|
||||
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
|
||||
log.Errorf("Cannot set ulimit for nofile: %s", err.Error())
|
||||
}
|
||||
|
||||
rLimit.Max = defaultULimit
|
||||
rLimit.Cur = rLimit.Max
|
||||
if err := syscall.Setrlimit(syscall.RLIMIT_STACK, &rLimit); err != nil {
|
||||
log.Errorf("Cannot set ulimit for stack: %s ", err.Error())
|
||||
}
|
||||
|
||||
if err := syscall.Setrlimit(syscall.RLIMIT_CORE, &rLimit); err != nil {
|
||||
log.Errorf("Cannot set ulimit for core blocks: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := syscall.Setrlimit(unix.RLIMIT_MEMLOCK, &rLimit); err != nil {
|
||||
log.Errorf("Cannot set ulimit for memlock: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := syscall.Setrlimit(unix.RLIMIT_NPROC, &rLimit); err != nil {
|
||||
log.Errorf("Cannot set ulimit for nproc: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// invoke will invoke the closure returned from the tether netfilter prep and
|
||||
// block until complete. It handles both potential preparation errors and invocation
|
||||
// errors.
|
||||
// the 'task' specified is used to construct error messages with the specific operation
|
||||
// embedded
|
||||
func invoke(t *tether.BaseOperations, fn tether.UtilityFn, task string) error {
|
||||
exitChan, err := t.LaunchUtility(fn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s failed: %s", task, err)
|
||||
}
|
||||
|
||||
exitCode := <-exitChan
|
||||
if exitCode != 0 {
|
||||
return fmt.Errorf("%s returned non-zero: %d", task, exitCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupFirewall sets up firewall rules on the external scope only. Any
|
||||
// portmaps are honored as are port exposes.
|
||||
func (t *operations) SetupFirewall(ctx context.Context, config *tether.ExecutorConfig) error {
|
||||
return setupFirewall(ctx, &t.BaseOperations, config)
|
||||
}
|
||||
|
||||
// setupFirewall is broken out from SetupFirewall so that it can be referenced from the test code
|
||||
func setupFirewall(ctx context.Context, t *tether.BaseOperations, config *tether.ExecutorConfig) error {
|
||||
fn := netfilter.Flush(ctx, "VIC")
|
||||
if err := invoke(t, fn, "flush"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range config.Networks {
|
||||
switch endpoint.Network.Type {
|
||||
case constants.ExternalScopeType:
|
||||
id, err := strconv.Atoi(endpoint.ID)
|
||||
if err != nil {
|
||||
log.Errorf("can't apply port rules: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
iface, err := t.LinkBySlot(int32(id))
|
||||
if err != nil {
|
||||
log.Errorf("can't apply rules: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
ifaceName := iface.Attrs().Name
|
||||
log.Debugf("slot %d -> %s", endpoint.ID, ifaceName)
|
||||
|
||||
// ensure that we can pass DHCP traffic if it's necessary
|
||||
if endpoint.Network.TrustLevel != executor.Open && endpoint.IsDynamic() {
|
||||
allowDHCPTraffic(ctx, t, ifaceName)
|
||||
}
|
||||
|
||||
switch endpoint.Network.TrustLevel {
|
||||
case executor.Open:
|
||||
// Configure port redirect in Open deployment
|
||||
if err := setupPorts(ctx, t, endpoint, ifaceName, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Accept all incoming and outgoing traffic
|
||||
for _, chain := range []netfilter.Chain{netfilter.Input, netfilter.Output, netfilter.Forward} {
|
||||
fn := (&netfilter.Rule{
|
||||
Chain: chain,
|
||||
Target: netfilter.Accept,
|
||||
Interface: ifaceName,
|
||||
}).Commit(ctx)
|
||||
if err := invoke(t, fn, "accept all"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case executor.Closed:
|
||||
// Reject all incoming and outgoing traffic
|
||||
// Since our default policy is to drop traffic, nothing is needed here.
|
||||
|
||||
case executor.Outbound:
|
||||
// Reject all incoming traffic, but allow outgoing
|
||||
if err := setupOutboundFirewall(ctx, t, ifaceName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case executor.Peers:
|
||||
// Outbound + all ports open to source addresses in --container-network-ip-range
|
||||
if err := setupOutboundFirewall(ctx, t, ifaceName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Configure port redirect in Peer deployment
|
||||
if err := setupPorts(ctx, t, endpoint, ifaceName, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sourceAddresses := make([]string, len(endpoint.Network.Pools))
|
||||
for i, v := range endpoint.Network.Pools {
|
||||
sourceAddresses[i] = v.String()
|
||||
}
|
||||
fn := (&netfilter.Rule{
|
||||
Chain: netfilter.Input,
|
||||
Target: netfilter.Accept,
|
||||
SourceAddresses: sourceAddresses,
|
||||
Interface: ifaceName,
|
||||
}).Commit(ctx)
|
||||
if err := invoke(t, fn, "allow outbound and peers"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := allowPingTraffic(ctx, t, ifaceName, sourceAddresses); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
// covers executor.Published and executor.Unspecified as well as invalid values
|
||||
log.Infof("Applying published rules for configuration %v", endpoint.Network.TrustLevel)
|
||||
|
||||
if err := setupOutboundFirewall(ctx, t, ifaceName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := allowPingTraffic(ctx, t, ifaceName, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setupPorts(ctx, t, endpoint, ifaceName, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case constants.BridgeScopeType:
|
||||
id, err := strconv.Atoi(endpoint.ID)
|
||||
if err != nil {
|
||||
log.Errorf("can't apply port rules: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
iface, err := t.LinkBySlot(int32(id))
|
||||
if err != nil {
|
||||
log.Errorf("can't apply rules: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
ifaceName := iface.Attrs().Name
|
||||
log.Debugf("slot %d -> %s", endpoint.ID, ifaceName)
|
||||
|
||||
// Traffic over container bridge network should be peers+outbound.
|
||||
if err := setupOutboundFirewall(ctx, t, ifaceName); err != nil {
|
||||
return err
|
||||
}
|
||||
sourceAddresses := make([]string, len(endpoint.Network.Pools))
|
||||
|
||||
for i, v := range endpoint.Network.Pools {
|
||||
sourceAddresses[i] = v.String()
|
||||
}
|
||||
fn := (&netfilter.Rule{
|
||||
Chain: netfilter.Input,
|
||||
Target: netfilter.Accept,
|
||||
SourceAddresses: sourceAddresses,
|
||||
Interface: ifaceName,
|
||||
}).Commit(ctx)
|
||||
if err := invoke(t, fn, "configure for bridge scope"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return invoke(t, netfilter.Return(ctx, "VIC"), "return from VIC chain")
|
||||
}
|
||||
|
||||
func setupOutboundFirewall(ctx context.Context, t *tether.BaseOperations, ifaceName string) error {
|
||||
// All already established inputs are accepted
|
||||
fn := (&netfilter.Rule{
|
||||
Chain: netfilter.Input,
|
||||
States: []netfilter.State{netfilter.Established, netfilter.Related},
|
||||
Target: netfilter.Accept,
|
||||
Interface: ifaceName,
|
||||
}).Commit(ctx)
|
||||
if err := invoke(t, fn, "permit established inbound"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// All output is accepted
|
||||
fn = (&netfilter.Rule{
|
||||
Chain: netfilter.Output,
|
||||
Target: netfilter.Accept,
|
||||
Interface: ifaceName,
|
||||
}).Commit(ctx)
|
||||
return invoke(t, fn, "permit all outbound")
|
||||
}
|
||||
|
||||
func setupPorts(ctx context.Context, t *tether.BaseOperations, endpoint *tether.NetworkEndpoint, ifaceName string, redirectOnly bool) error {
|
||||
// handle the ports
|
||||
for _, p := range endpoint.Ports {
|
||||
// parse the port maps
|
||||
rules, err := portToRule(p, redirectOnly)
|
||||
if err != nil {
|
||||
log.Errorf("can't apply port rule (%s): %s", p, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("Applying %d rules for port %s", len(rules), p)
|
||||
for _, r := range rules {
|
||||
r.Interface = ifaceName
|
||||
fn := r.Commit(ctx)
|
||||
if err := invoke(t, fn, "allow incoming on published port"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func allowPingTraffic(ctx context.Context, t *tether.BaseOperations, ifaceName string, sourceAddresses []string) error {
|
||||
fn := (&netfilter.Rule{
|
||||
Chain: netfilter.Input,
|
||||
Target: netfilter.Accept,
|
||||
Interface: ifaceName,
|
||||
Protocol: netfilter.ICMP,
|
||||
ICMPType: netfilter.EchoRequest,
|
||||
SourceAddresses: sourceAddresses,
|
||||
}).Commit(ctx)
|
||||
if err := invoke(t, fn, "allow ping inbound"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fn = (&netfilter.Rule{
|
||||
Chain: netfilter.Output,
|
||||
Target: netfilter.Accept,
|
||||
Interface: ifaceName,
|
||||
Protocol: netfilter.ICMP,
|
||||
ICMPType: netfilter.EchoReply,
|
||||
SourceAddresses: sourceAddresses,
|
||||
}).Commit(ctx)
|
||||
return invoke(t, fn, "allow ping outbound")
|
||||
}
|
||||
|
||||
func allowDHCPTraffic(ctx context.Context, t *tether.BaseOperations, ifaceName string) error {
|
||||
fn := (&netfilter.Rule{
|
||||
Chain: netfilter.Input,
|
||||
Target: netfilter.Accept,
|
||||
Interface: ifaceName,
|
||||
Protocol: netfilter.UDP,
|
||||
FromPort: "67:68",
|
||||
SrcPort: "67:68",
|
||||
}).Commit(ctx)
|
||||
if err := invoke(t, fn, "allow dhcp inbound"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fn = (&netfilter.Rule{
|
||||
Chain: netfilter.Output,
|
||||
Target: netfilter.Accept,
|
||||
Interface: ifaceName,
|
||||
Protocol: netfilter.UDP,
|
||||
FromPort: "67:68",
|
||||
SrcPort: "67:68",
|
||||
}).Commit(ctx)
|
||||
return invoke(t, fn, "allow dhcp outbound")
|
||||
}
|
||||
|
||||
func portToRule(p string, redirectOnly bool) ([]*netfilter.Rule, error) {
|
||||
// 9999/tcp
|
||||
s := strings.Split(p, "/")
|
||||
if len(s) != 2 {
|
||||
return nil, errors.New("can't parse port spec: " + p)
|
||||
}
|
||||
|
||||
proto := netfilter.Protocol(s[1])
|
||||
switch proto {
|
||||
case netfilter.UDP:
|
||||
case netfilter.TCP:
|
||||
default:
|
||||
return nil, errors.New("unknown protocol")
|
||||
}
|
||||
|
||||
mapping := strings.Split(s[0], ":")
|
||||
directPort := mapping[len(mapping)-1]
|
||||
|
||||
// publish the actual port directly
|
||||
var rules []*netfilter.Rule
|
||||
expose := &netfilter.Rule{
|
||||
Chain: netfilter.Input,
|
||||
Interface: "external",
|
||||
Target: netfilter.Accept,
|
||||
Protocol: proto,
|
||||
// ranges are specified using a hyphen in docker
|
||||
FromPort: strings.Replace(directPort, "-", ":", -1),
|
||||
}
|
||||
|
||||
if !redirectOnly {
|
||||
rules = append(rules, expose)
|
||||
}
|
||||
|
||||
// if there's no redirection we're done
|
||||
if len(mapping) == 1 || mapping[0] == mapping[1] {
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
// redirect port
|
||||
// https://wiki.debian.org/Firewalls-local-port-redirection contains a useful reference
|
||||
|
||||
if strings.Contains(s[0], "-") {
|
||||
return nil, errors.New("cannot port forward a range")
|
||||
}
|
||||
|
||||
rules = append(rules, &netfilter.Rule{
|
||||
Table: netfilter.Nat,
|
||||
Chain: netfilter.Prerouting,
|
||||
Interface: "external",
|
||||
Target: netfilter.Redirect,
|
||||
Protocol: proto,
|
||||
FromPort: mapping[0],
|
||||
ToPort: mapping[1],
|
||||
})
|
||||
|
||||
return rules, nil
|
||||
}
|
||||
161
vendor/github.com/vmware/vic/cmd/tether/ops_linux_test.go
generated
vendored
Normal file
161
vendor/github.com/vmware/vic/cmd/tether/ops_linux_test.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// This has been copied from lib/tether/ but should be split into a common base package that is
|
||||
// dedicated to mocking these operations. We cannot reference lib/tether/*_test elements from
|
||||
// outside that package.
|
||||
type Interface struct {
|
||||
netlink.LinkAttrs
|
||||
Up bool
|
||||
Addrs []netlink.Addr
|
||||
}
|
||||
|
||||
func (t *Interface) Attrs() *netlink.LinkAttrs {
|
||||
return &t.LinkAttrs
|
||||
}
|
||||
|
||||
func (t *Interface) Type() string {
|
||||
return "mocked"
|
||||
}
|
||||
|
||||
func (t *Mocker) LinkByName(name string) (netlink.Link, error) {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Getting link by name %s", name)))
|
||||
|
||||
return t.Interfaces[name], nil
|
||||
}
|
||||
|
||||
func (t *Mocker) LinkSetName(link netlink.Link, name string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Renaming %s to %s", link.Attrs().Name, name)))
|
||||
|
||||
iface := link.(*Interface)
|
||||
_, ok := t.Interfaces[name]
|
||||
if ok {
|
||||
return fmt.Errorf("Interface with name %s already exists", name)
|
||||
}
|
||||
|
||||
oldName := iface.Name
|
||||
iface.Name = name
|
||||
// make sure there's no period where the interface isn't "present"
|
||||
t.Interfaces[name] = iface
|
||||
delete(t.Interfaces, oldName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) LinkSetDown(link netlink.Link) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Bringing %s down", link.Attrs().Name)))
|
||||
|
||||
iface := link.(*Interface)
|
||||
iface.Up = false
|
||||
// TODO: should this drop addresses?
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) LinkSetUp(link netlink.Link) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Bringing %s up", link.Attrs().Name)))
|
||||
|
||||
iface := link.(*Interface)
|
||||
iface.Up = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) LinkSetAlias(link netlink.Link, alias string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Adding alias %s to %s", alias, link.Attrs().Name)))
|
||||
|
||||
iface := link.(*Interface)
|
||||
iface.Alias = alias
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) AddrList(link netlink.Link, family int) ([]netlink.Addr, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
iface := link.(*Interface)
|
||||
return iface.Addrs, nil
|
||||
}
|
||||
|
||||
func (t *Mocker) AddrAdd(link netlink.Link, addr *netlink.Addr) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Adding %s to %s", addr.String(), link.Attrs().Name)))
|
||||
|
||||
iface := link.(*Interface)
|
||||
|
||||
for _, adr := range iface.Addrs {
|
||||
if addr.IP.String() == adr.IP.String() {
|
||||
return syscall.EEXIST
|
||||
}
|
||||
}
|
||||
|
||||
iface.Addrs = append(iface.Addrs, *addr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) AddrDel(link netlink.Link, addr *netlink.Addr) error {
|
||||
iface := link.(*Interface)
|
||||
|
||||
for i, adr := range iface.Addrs {
|
||||
if addr.IP.String() == adr.IP.String() {
|
||||
iface.Addrs = append(iface.Addrs[:i], iface.Addrs[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return syscall.EADDRNOTAVAIL
|
||||
}
|
||||
|
||||
func (t *Mocker) RouteAdd(route *netlink.Route) error {
|
||||
defer trace.End(trace.Begin("no implemented"))
|
||||
|
||||
// currently ignored
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) RouteDel(route *netlink.Route) error {
|
||||
defer trace.End(trace.Begin("no implemented"))
|
||||
|
||||
// currently ignored
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) RuleList(int) ([]netlink.Rule, error) {
|
||||
defer trace.End(trace.Begin("not implemented"))
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (t *Mocker) LinkBySlot(slot int32) (netlink.Link, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
id := int(slot)
|
||||
for _, intf := range t.Interfaces {
|
||||
if intf.Attrs().Index == id {
|
||||
return intf, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("no such interface")
|
||||
}
|
||||
300
vendor/github.com/vmware/vic/cmd/tether/tether_test.go
generated
vendored
Normal file
300
vendor/github.com/vmware/vic/cmd/tether/tether_test.go
generated
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/system"
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
"github.com/vmware/vic/pkg/dio"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
// Copied from lib/tether
|
||||
// because there's no easy way to use test code from other packages and a separate
|
||||
// package causes cyclic dependencies.
|
||||
// Some modifications to deal with the change of package and attach usage
|
||||
|
||||
var Mocked Mocker
|
||||
|
||||
type Mocker struct {
|
||||
Base tether.BaseOperations
|
||||
|
||||
// allow tests to tell when the tether has finished setup
|
||||
Started chan bool
|
||||
// allow tests to tell when the tether has finished
|
||||
Cleaned chan bool
|
||||
|
||||
// debug output gets logged here
|
||||
LogBuffer bytes.Buffer
|
||||
|
||||
// session output gets logged here
|
||||
SessionLogBuffer bytes.Buffer
|
||||
|
||||
// track the number of interfaces for a run
|
||||
maxSlot int
|
||||
// the interfaces in the system indexed by name
|
||||
Interfaces map[string]netlink.Link
|
||||
|
||||
// the hostname of the system
|
||||
Hostname string
|
||||
// the ip configuration for name index networks
|
||||
IPs map[string]net.IP
|
||||
// filesystem mounts, indexed by disk label
|
||||
Mounts map[string]string
|
||||
|
||||
WindowCol uint32
|
||||
WindowRow uint32
|
||||
Signal ssh.Signal
|
||||
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// Start implements the extension method
|
||||
func (t *Mocker) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop implements the extension method
|
||||
func (t *Mocker) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload implements the extension method
|
||||
func (t *Mocker) Reload(config *tether.ExecutorConfig) error {
|
||||
t.once.Do(func() {
|
||||
// the tether has definitely finished it's startup by the time we hit this
|
||||
close(t.Started)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) Setup(config tether.Config) error {
|
||||
return t.Base.Setup(config)
|
||||
}
|
||||
|
||||
func (t *Mocker) Cleanup() error {
|
||||
err := t.Base.Cleanup()
|
||||
close(t.Cleaned)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Mocker) Log() (io.Writer, error) {
|
||||
return os.Stdout, nil
|
||||
}
|
||||
|
||||
func (t *Mocker) SessionLog(session *tether.SessionConfig) (dio.DynamicMultiWriter, dio.DynamicMultiWriter, error) {
|
||||
return dio.MultiWriter(&t.SessionLogBuffer), dio.MultiWriter(&t.SessionLogBuffer), nil
|
||||
}
|
||||
|
||||
func (t *Mocker) HandleSessionExit(config *tether.ExecutorConfig, session *tether.SessionConfig) func() {
|
||||
// check for executor behaviour
|
||||
return func() {
|
||||
if session.ID == config.ID {
|
||||
tthr.Stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Mocker) ProcessEnv(env []string) []string {
|
||||
return t.Base.ProcessEnv(env)
|
||||
}
|
||||
|
||||
// SetHostname sets both the kernel hostname and /etc/hostname to the specified string
|
||||
func (t *Mocker) SetHostname(hostname string, aliases ...string) error {
|
||||
defer trace.End(trace.Begin("mocking hostname to " + hostname))
|
||||
|
||||
// TODO: we could mock at a much finer granularity, only extracting the syscall
|
||||
// that would exercise the file modification paths, however it's much less generalizable
|
||||
t.Hostname = hostname
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) SetupFirewall(ctx context.Context, config *tether.ExecutorConfig) error {
|
||||
err := setupFirewall(ctx, &t.Base, config)
|
||||
// NOTE: we squash errors from here for now because it's almost certain to fail
|
||||
// in absence of a mock for the iptables update
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply takes the network endpoint configuration and applies it to the system
|
||||
func (t *Mocker) Apply(endpoint *tether.NetworkEndpoint) error {
|
||||
return tether.ApplyEndpoint(t, &t.Base, endpoint)
|
||||
}
|
||||
|
||||
// MountLabel performs a mount with the source treated as a disk label
|
||||
// This assumes that /dev/disk/by-label is being populated, probably by udev
|
||||
func (t *Mocker) MountLabel(ctx context.Context, label, target string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("mocking mounting %s on %s", label, target)))
|
||||
|
||||
if t.Mounts == nil {
|
||||
t.Mounts = make(map[string]string)
|
||||
}
|
||||
|
||||
t.Mounts[label] = target
|
||||
return nil
|
||||
}
|
||||
|
||||
// MountTarget performs a mount with the source treated as an nfs target
|
||||
func (t *Mocker) MountTarget(ctx context.Context, source url.URL, target string, mountOptions string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("mocking mounting %s on %s", source.String(), target)))
|
||||
|
||||
if t.Mounts == nil {
|
||||
t.Mounts = make(map[string]string)
|
||||
}
|
||||
|
||||
t.Mounts[source.String()] = target
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyExistingContent copies the underlying files shadowed by a mount on a directory
|
||||
// to the volume mounted on the directory
|
||||
func (t *Mocker) CopyExistingContent(source string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("mocking copyExistingContent from %s", source)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fork triggers vmfork and handles the necessary pre/post OS level operations
|
||||
func (t *Mocker) Fork() error {
|
||||
return errors.New("Fork test not implemented")
|
||||
}
|
||||
|
||||
// LaunchUtility uses the underlying implementation for launching and tracking utility processes
|
||||
func (t *Mocker) LaunchUtility(fn tether.UtilityFn) (<-chan int, error) {
|
||||
return t.Base.LaunchUtility(fn)
|
||||
}
|
||||
|
||||
func (t *Mocker) HandleUtilityExit(pid, exitCode int) bool {
|
||||
return t.Base.HandleUtilityExit(pid, exitCode)
|
||||
}
|
||||
|
||||
// TestMain simply so we have control of debugging level and somewhere to call package wide test setup
|
||||
func TestMain(m *testing.M) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
retCode := m.Run()
|
||||
|
||||
// call with result of m.Run()
|
||||
os.Exit(retCode)
|
||||
}
|
||||
|
||||
func StartTether(t *testing.T, cfg *executor.ExecutorConfig, mocker *Mocker) (tether.Tether, extraconfig.DataSource, extraconfig.DataSink) {
|
||||
store := extraconfig.New()
|
||||
sink := store.Put
|
||||
src := store.Get
|
||||
extraconfig.Encode(sink, cfg)
|
||||
log.Debugf("Test configuration: %#v", sink)
|
||||
|
||||
tthr = tether.New(src, sink, mocker)
|
||||
tthr.Register("mocker", mocker)
|
||||
|
||||
// run the tether to service the attach
|
||||
go func() {
|
||||
erR := tthr.Start()
|
||||
if erR != nil {
|
||||
t.Error(erR)
|
||||
}
|
||||
}()
|
||||
|
||||
return tthr, src, sink
|
||||
}
|
||||
|
||||
func StartAttachTether(t *testing.T, cfg *executor.ExecutorConfig, mocker *Mocker) (tether.Tether, extraconfig.DataSource, net.Conn) {
|
||||
store := extraconfig.New()
|
||||
sink := store.Put
|
||||
src := store.Get
|
||||
extraconfig.Encode(sink, cfg)
|
||||
log.Debugf("Test configuration: %#v", sink)
|
||||
|
||||
tthr = tether.New(src, sink, mocker)
|
||||
tthr.Register("mocker", mocker)
|
||||
tthr.Register("Attach", server)
|
||||
|
||||
// run the tether to service the attach
|
||||
go func() {
|
||||
erR := tthr.Start()
|
||||
if erR != nil {
|
||||
t.Error(erR)
|
||||
}
|
||||
}()
|
||||
|
||||
// create client on the mock pipe
|
||||
conn, err := mockBackChannel(context.Background())
|
||||
if err != nil && (err != io.EOF || server.(*testAttachServer).enabled) {
|
||||
// we accept the case where the error is end-of-file and the attach server is disabled because that's
|
||||
// expected when the tether is shut down.
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
return tthr, src, conn
|
||||
}
|
||||
|
||||
func tetherTestSetup(t *testing.T) *Mocker {
|
||||
log.Infof("Started test setup for %s", t.Name())
|
||||
|
||||
// use the mock ops - fresh one each time as tests might apply different mocked calls
|
||||
mocker := Mocker{
|
||||
Started: make(chan bool),
|
||||
Cleaned: make(chan bool),
|
||||
Interfaces: make(map[string]netlink.Link, 0),
|
||||
}
|
||||
|
||||
// replace the Sys variable with a mock
|
||||
tether.Sys = system.NewWithRoot(pathPrefix)
|
||||
tether.Sys.Syscall = &tether.MockSyscall{}
|
||||
|
||||
tether.BindSys = system.NewWithRoot(path.Join(pathPrefix, "/.tether"))
|
||||
tether.BindSys.Syscall = &tether.MockSyscall{}
|
||||
|
||||
// ensure that the sys and bindsys root exists
|
||||
// #nosec
|
||||
os.MkdirAll(tether.Sys.Root, 0700)
|
||||
// #nosec
|
||||
os.MkdirAll(tether.BindSys.Root, 0700)
|
||||
|
||||
return &mocker
|
||||
}
|
||||
|
||||
func tetherTestTeardown(t *testing.T, mocker *Mocker) {
|
||||
<-mocker.Cleaned
|
||||
|
||||
// cleanup
|
||||
os.RemoveAll(pathPrefix)
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
log.Infof("Finished test teardown for %s", t.Name())
|
||||
}
|
||||
89
vendor/github.com/vmware/vic/cmd/vic-dns/main.go
generated
vendored
Normal file
89
vendor/github.com/vmware/vic/cmd/vic-dns/main.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"syscall"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/dns"
|
||||
vlog "github.com/vmware/vic/pkg/log"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
)
|
||||
|
||||
var (
|
||||
options = dns.ServerOptions{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&options.IP, "ip", dns.DefaultIP, "IP to use")
|
||||
flag.IntVar(&options.Port, "port", dns.DefaultPort, "Port to bind")
|
||||
flag.StringVar(&options.Interface, "interface", "", "Interface to bind")
|
||||
|
||||
flag.Var(&options.Nameservers, "nameservers", "Nameservers to use")
|
||||
|
||||
flag.DurationVar(&options.Timeout, "timeout", dns.DefaultTimeout, "Timeout for external DNS queries")
|
||||
|
||||
flag.DurationVar(&options.TTL, "ttl", dns.DefaultTTL, "TTL")
|
||||
flag.IntVar(&options.CacheSize, "cachesize", dns.DefaultCacheSize, "Cache size to use")
|
||||
|
||||
flag.BoolVar(&options.Debug, "debug", false, "Enable debugging")
|
||||
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, fmt.Sprintf("%s : %s", r, debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
if version.Show() {
|
||||
// #nosec: Errors unhandled.
|
||||
fmt.Fprintf(os.Stdout, "%s\n", version.String())
|
||||
return
|
||||
}
|
||||
|
||||
// Initiliaze logger with default TextFormatter
|
||||
log.SetFormatter(vlog.NewTextFormatter())
|
||||
|
||||
// Set the log level
|
||||
if options.Debug {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
server := dns.NewServer(options)
|
||||
if server != nil {
|
||||
server.Start()
|
||||
}
|
||||
|
||||
// handle the signals and gracefully shutdown the server
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
log.Warnf("signal %s received", <-sig)
|
||||
server.Stop()
|
||||
}()
|
||||
|
||||
server.Wait()
|
||||
}
|
||||
30
vendor/github.com/vmware/vic/cmd/vic-init/config.go
generated
vendored
Normal file
30
vendor/github.com/vmware/vic/cmd/vic-init/config.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import "github.com/vmware/vic/lib/config/executor"
|
||||
|
||||
type ExecutorConfig struct {
|
||||
// Diagnostics holds basic diagnostics data
|
||||
Diagnostics Diagnostics `vic:"0.1" scope:"read-only" key:"diagnostics"`
|
||||
}
|
||||
|
||||
type Diagnostics struct {
|
||||
// Should debugging be enabled on whatever component this is and at what level
|
||||
DebugLevel int `vic:"0.1" scope:"read-only" key:"debug"`
|
||||
// SyslogConfig holds configuration for connecting to a syslog
|
||||
// server
|
||||
SysLogConfig *executor.SysLogConfig `vic:"0.1" scope:"read-only" key:"syslog"`
|
||||
}
|
||||
203
vendor/github.com/vmware/vic/cmd/vic-init/main_linux.go
generated
vendored
Normal file
203
vendor/github.com/vmware/vic/cmd/vic-init/main_linux.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/vmware/govmomi/toolbox"
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
viclog "github.com/vmware/vic/pkg/log"
|
||||
"github.com/vmware/vic/pkg/log/syslog"
|
||||
"github.com/vmware/vic/pkg/logmgr"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
var (
|
||||
tthr tether.Tether
|
||||
config ExecutorConfig
|
||||
debugLevel int
|
||||
)
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Errorf("run time panic: %s : %s", r, debug.Stack())
|
||||
}
|
||||
|
||||
reboot()
|
||||
}()
|
||||
|
||||
// ensure that panics and error output are persisted
|
||||
logFile, err := os.OpenFile("/dev/ttyS0", os.O_WRONLY|os.O_SYNC, 0)
|
||||
if err != nil {
|
||||
log.Errorf("Could not redirect outputs to serial for debugging info, some debug info may be lost! Error reported was %s", err)
|
||||
}
|
||||
|
||||
err = syscall.Dup3(int(logFile.Fd()), int(os.Stderr.Fd()), 0)
|
||||
if err != nil {
|
||||
log.Errorf("Could not pipe standard error to logfile: %s", err)
|
||||
}
|
||||
_, err = os.Stderr.WriteString("all stderr redirected to debug log")
|
||||
if err != nil {
|
||||
log.Errorf("Could not write to Stderr due to error %s", err)
|
||||
}
|
||||
|
||||
err = syscall.Dup3(int(logFile.Fd()), int(os.Stdout.Fd()), 0)
|
||||
if err != nil {
|
||||
log.Errorf("Could not pipe standard out to logfile: %s", err)
|
||||
}
|
||||
_, err = os.Stderr.WriteString("all stdout redirected to debug log")
|
||||
if err != nil {
|
||||
log.Errorf("Could not write to stdout due to error %s", err)
|
||||
}
|
||||
|
||||
src, err := extraconfig.GuestInfoSourceWithPrefix("init")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
extraconfig.Decode(src, &config)
|
||||
debugLevel = config.Diagnostics.DebugLevel
|
||||
|
||||
logcfg := viclog.NewLoggingConfig()
|
||||
if debugLevel > 0 {
|
||||
logcfg.Level = log.DebugLevel
|
||||
trace.Logger.Level = log.DebugLevel
|
||||
syslog.Logger.Level = log.DebugLevel
|
||||
}
|
||||
|
||||
if config.Diagnostics.SysLogConfig != nil {
|
||||
logcfg.Syslog = &viclog.SyslogConfig{
|
||||
Network: config.Diagnostics.SysLogConfig.Network,
|
||||
RAddr: config.Diagnostics.SysLogConfig.RAddr,
|
||||
Priority: syslog.Info | syslog.Daemon,
|
||||
}
|
||||
}
|
||||
|
||||
viclog.Init(logcfg)
|
||||
|
||||
if debugLevel > 2 {
|
||||
enableShell()
|
||||
}
|
||||
|
||||
sink, err := extraconfig.GuestInfoSinkWithPrefix("init")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// create the tether
|
||||
tthr = tether.New(src, sink, &operations{})
|
||||
|
||||
// register the toolbox extension and configure for appliance
|
||||
toolbox := configureToolbox(tether.NewToolbox())
|
||||
toolbox.PrimaryIP = externalIP
|
||||
tthr.Register("Toolbox", toolbox)
|
||||
|
||||
// Check logs every 5 minutes and rotate them if their size exceeds 20MB.
|
||||
// The history size we keep is 2 previous files in a compressed form.
|
||||
// TODO: Check available memory to tune log size and history length for log files.
|
||||
logrotate, err := logmgr.NewLogManager(time.Second * 300)
|
||||
const maxLogSizeBytes = 20 * 1024 * 1024
|
||||
if err == nil {
|
||||
logrotate.AddLogRotate("/var/log/vic/port-layer.log", logmgr.Daily, maxLogSizeBytes, 2, true)
|
||||
logrotate.AddLogRotate("/var/log/vic/init.log", logmgr.Daily, maxLogSizeBytes, 2, true)
|
||||
logrotate.AddLogRotate("/var/log/vic/docker-personality.log", logmgr.Daily, maxLogSizeBytes, 2, true)
|
||||
logrotate.AddLogRotate("/var/log/vic/vicadmin.log", logmgr.Daily, maxLogSizeBytes, 2, true)
|
||||
tthr.Register("logrotate", logrotate)
|
||||
} else {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
err = tthr.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Info("Clean exit from init")
|
||||
}
|
||||
|
||||
// exit cleanly shuts down the system
|
||||
func halt() {
|
||||
log.Infof("Powering off the system")
|
||||
if debugLevel > 0 {
|
||||
log.Info("Squashing power off for debug init")
|
||||
return
|
||||
}
|
||||
|
||||
syscall.Sync()
|
||||
syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF)
|
||||
}
|
||||
|
||||
func reboot() {
|
||||
log.Infof("Rebooting the system")
|
||||
if debugLevel > 0 {
|
||||
log.Info("Squashing reboot for debug init")
|
||||
return
|
||||
}
|
||||
|
||||
syscall.Sync()
|
||||
syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART)
|
||||
}
|
||||
|
||||
func configureToolbox(t *tether.Toolbox) *tether.Toolbox {
|
||||
cmd := t.Service.Command
|
||||
cmd.ProcessStartCommand = startCommand
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// externalIP attempts to find an external IP to be reported as the guest IP
|
||||
func externalIP() string {
|
||||
l, err := netlink.LinkByName("client")
|
||||
if err != nil {
|
||||
log.Debugf("error looking up client interface by name: %s", err)
|
||||
l, err = netlink.LinkByAlias("client")
|
||||
if err != nil {
|
||||
log.Errorf("error looking up client interface by alias: %s", err)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
addrs, err := netlink.AddrList(l, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
log.Errorf("error getting address list for client interface: %s", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(addrs) == 0 {
|
||||
log.Warnf("no addresses set on client interface")
|
||||
return ""
|
||||
}
|
||||
|
||||
return addrs[0].IP.String()
|
||||
}
|
||||
|
||||
// defaultIP tries externalIP, falling back to toolbox.DefaultIP()
|
||||
func defaultIP() string {
|
||||
ip := externalIP()
|
||||
if ip != "" {
|
||||
return ip
|
||||
}
|
||||
|
||||
return toolbox.DefaultIP()
|
||||
}
|
||||
32
vendor/github.com/vmware/vic/cmd/vic-init/main_test.go
generated
vendored
Normal file
32
vendor/github.com/vmware/vic/cmd/vic-init/main_test.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var systemTest *bool
|
||||
|
||||
func init() {
|
||||
systemTest = flag.Bool("systemTest", false, "Run system test")
|
||||
}
|
||||
|
||||
func TestSystem(t *testing.T) {
|
||||
if *systemTest {
|
||||
main()
|
||||
}
|
||||
}
|
||||
140
vendor/github.com/vmware/vic/cmd/vic-init/ops.go
generated
vendored
Normal file
140
vendor/github.com/vmware/vic/cmd/vic-init/ops.go
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/pprof"
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
"github.com/vmware/vic/pkg/dio"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// pathPrefix is present to allow the various files referenced by tether to be placed
|
||||
// in specific directories, primarily for testing.
|
||||
var pathPrefix string
|
||||
|
||||
const (
|
||||
logDir = "var/log/vic"
|
||||
initLog = "init.log"
|
||||
)
|
||||
|
||||
type operations struct {
|
||||
tether.BaseOperations
|
||||
}
|
||||
|
||||
func (t *operations) Setup(sink tether.Config) error {
|
||||
if err := t.BaseOperations.Setup(sink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return pprof.StartPprof("vch-init", pprof.VCHInitPort)
|
||||
}
|
||||
|
||||
func (t *operations) Cleanup() error {
|
||||
return t.BaseOperations.Cleanup()
|
||||
}
|
||||
|
||||
// HandleSessionExit controls the behaviour on session exit - for the tether if the session exiting
|
||||
// is the primary session (i.e. SessionID matches ExecutorID) then we exit everything.
|
||||
func (t *operations) HandleSessionExit(config *tether.ExecutorConfig, session *tether.SessionConfig) func() {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// trigger a reload to force relaunch
|
||||
return func() {
|
||||
// If executor debug is greater than 1 then suppress the relaunch but leave the executor up
|
||||
// for diagnostics
|
||||
if config.DebugLevel > 2 {
|
||||
log.Warnf("Debug is set to %d so squashing relaunch of exited process", config.DebugLevel)
|
||||
return
|
||||
}
|
||||
|
||||
// incredibly basic throttle
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
tthr.Reload()
|
||||
log.Info("Triggered reload")
|
||||
}
|
||||
}
|
||||
|
||||
func (t *operations) SetHostname(name string, aliases ...string) error {
|
||||
// switch the names around so we get the pretty name and not the ID
|
||||
return t.BaseOperations.SetHostname(aliases[0])
|
||||
}
|
||||
|
||||
func (t *operations) Apply(endpoint *tether.NetworkEndpoint) error {
|
||||
return t.BaseOperations.Apply(endpoint)
|
||||
}
|
||||
|
||||
// Log will redirect logging to both a file on disk and to stdout for the process
|
||||
func (t *operations) Log() (io.Writer, error) {
|
||||
defer trace.End(trace.Begin("operations.Log"))
|
||||
|
||||
// make the logging directory
|
||||
// #nosec: Expect directory permissions to be 0700 or less
|
||||
os.MkdirAll(path.Join(pathPrefix, logDir), 0755)
|
||||
|
||||
logPath := path.Join(pathPrefix, logDir, initLog)
|
||||
|
||||
log.Infof("opening %s for debug log", logPath)
|
||||
// #nosec: Expect file permissions to be 0600 or less
|
||||
out, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_SYNC|syscall.O_NOCTTY, 0644)
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("failed to open file port for debug log: %s", err)
|
||||
log.Error(detail)
|
||||
return nil, errors.New(detail)
|
||||
}
|
||||
|
||||
return io.MultiWriter(out, os.Stdout), nil
|
||||
}
|
||||
|
||||
// sessionLogWriter returns a writer that will persist the session output
|
||||
func (t *operations) SessionLog(session *tether.SessionConfig) (dio.DynamicMultiWriter, dio.DynamicMultiWriter, error) {
|
||||
defer trace.End(trace.Begin("configure session log writer"))
|
||||
|
||||
name := session.ID
|
||||
if name == "" {
|
||||
name = session.Name
|
||||
}
|
||||
|
||||
logPath := path.Join(pathPrefix, logDir, name+".log")
|
||||
|
||||
// open SttyS2 for session logging
|
||||
log.Infof("opening %s for session logging", logPath)
|
||||
// #nosec: Expect file permissions to be 0600 or less
|
||||
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_SYNC|syscall.O_NOCTTY, 0644)
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("failed to open file for session log: %s", err)
|
||||
log.Error(detail)
|
||||
return nil, nil, errors.New(detail)
|
||||
}
|
||||
|
||||
// use multi-writer so it goes to both screen and session log
|
||||
if debugLevel > 0 {
|
||||
return dio.MultiWriter(f, os.Stdout), dio.MultiWriter(f, os.Stderr), nil
|
||||
}
|
||||
|
||||
// only duplicate stderr
|
||||
return dio.MultiWriter(f), dio.MultiWriter(f), nil
|
||||
}
|
||||
61
vendor/github.com/vmware/vic/cmd/vic-init/ops_linux.go
generated
vendored
Normal file
61
vendor/github.com/vmware/vic/cmd/vic-init/ops_linux.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/libnetwork/iptables"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
)
|
||||
|
||||
const (
|
||||
publicIfaceName = "public"
|
||||
)
|
||||
|
||||
func (t *operations) SetupFirewall(ctx context.Context, config *tether.ExecutorConfig) error {
|
||||
// get the public interface name
|
||||
l, err := netlink.LinkByName(publicIfaceName)
|
||||
if l == nil {
|
||||
l, err = netlink.LinkByAlias(publicIfaceName)
|
||||
if l == nil {
|
||||
return fmt.Errorf("could not find interface: %s", publicIfaceName)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = iptables.Raw(string(iptables.Append), "FORWARD", "-i", "bridge", "-o", l.Attrs().Name, "-j", "ACCEPT"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if config.AsymmetricRouting {
|
||||
// set rp_filter to "loose" mode; see https://en.wikipedia.org/wiki/Reverse_path_forwarding#Loose_mode
|
||||
//
|
||||
// this is so the kernel will not drop packets sent to the public interface from an
|
||||
// address that is reachable by other interfaces on the VCH. specifically, when
|
||||
// packets from the bridge network are directed to another network (say, management),
|
||||
// the incoming reply to the VCH can be dropped if rp_filter is set to the default 1
|
||||
if err = ioutil.WriteFile(fmt.Sprintf("/proc/sys/net/ipv4/conf/%s/rp_filter", l.Attrs().Name), []byte("2"), 0644); err != nil {
|
||||
// not a fatal error, so just log it here
|
||||
log.Warnf("error while setting rp_filter for interface %s: %s", l.Attrs().Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
117
vendor/github.com/vmware/vic/cmd/vic-init/restart_test.go
generated
vendored
Normal file
117
vendor/github.com/vmware/vic/cmd/vic-init/restart_test.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// TestPathLookup constructs the spec for a Session where the binary path must be
|
||||
// resolved from the PATH environment variable - this is a variation from normal
|
||||
// Cmd handling where that is done during creation of Cmd
|
||||
//
|
||||
|
||||
func TestRestart(t *testing.T) {
|
||||
testSetup(t)
|
||||
defer testTeardown(t)
|
||||
|
||||
cfg := executor.ExecutorConfig{
|
||||
ExecutorConfigCommon: executor.ExecutorConfigCommon{
|
||||
ID: "pathlookup",
|
||||
Name: "tether_test_executor",
|
||||
},
|
||||
Diagnostics: executor.Diagnostics{
|
||||
DebugLevel: 2,
|
||||
},
|
||||
Sessions: map[string]*executor.SessionConfig{
|
||||
"pathlookup": {
|
||||
Common: executor.Common{
|
||||
ID: "pathlookup",
|
||||
Name: "tether_test_session",
|
||||
},
|
||||
Tty: false,
|
||||
Active: true,
|
||||
|
||||
Cmd: executor.Cmd{
|
||||
// test relative path
|
||||
Path: "date",
|
||||
Args: []string{"date", "--reference=/"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
Dir: "/bin",
|
||||
},
|
||||
Restart: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tthr, src := StartTether(t, &cfg)
|
||||
|
||||
// wait for initialization
|
||||
<-Mocked.Started
|
||||
|
||||
result := &tether.ExecutorConfig{}
|
||||
extraconfig.Decode(src, result)
|
||||
|
||||
// Started returns when we reload but that doesn't mean that the process is started
|
||||
// Try multiple times before giving up
|
||||
for i := 0; i < 10; i++ {
|
||||
if result.Sessions["pathlookup"].Started != "" {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Duration(i) * time.Millisecond)
|
||||
}
|
||||
|
||||
assert.Equal(t, 0, result.Sessions["pathlookup"].ExitStatus, "Expected command to have exited cleanly")
|
||||
assert.True(t, result.Sessions["pathlookup"].Restart, "Expected command to be configured for restart")
|
||||
|
||||
// wait for the resurrection count to max out the channel
|
||||
for result.Sessions["pathlookup"].Diagnostics.ResurrectionCount < 10 {
|
||||
result = &tether.ExecutorConfig{}
|
||||
extraconfig.Decode(src, &result)
|
||||
assert.Equal(t, 0, result.Sessions["pathlookup"].ExitStatus, "Expected command to have exited cleanly")
|
||||
// proceed to the next reincarnation
|
||||
<-Mocked.SessionExit
|
||||
tthr.Reload()
|
||||
}
|
||||
|
||||
// read the output from the session
|
||||
log := Mocked.SessionLogBuffer.Bytes()
|
||||
|
||||
// the tether has to be stopped before comparison on the reaper may swaller exec.Wait
|
||||
tthr.Stop()
|
||||
<-Mocked.Cleaned
|
||||
|
||||
// run the command directly
|
||||
out, err := exec.Command("/bin/date", "--reference=/").Output()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to run date for comparison data: %s", err)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.True(t, strings.HasPrefix(string(log), string(out)), "Expected the data to be constant - first invocation doesn't match")
|
||||
assert.True(t, strings.HasSuffix(string(log), string(out)), "Expected the data to be constant - last invocation doesn't match")
|
||||
}
|
||||
283
vendor/github.com/vmware/vic/cmd/vic-init/tether_test.go
generated
vendored
Normal file
283
vendor/github.com/vmware/vic/cmd/vic-init/tether_test.go
generated
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/system"
|
||||
"github.com/vmware/vic/lib/tether"
|
||||
"github.com/vmware/vic/pkg/dio"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
var Mocked Mocker
|
||||
|
||||
type Mocker struct {
|
||||
Base tether.BaseOperations
|
||||
|
||||
// allow tests to tell when the tether has finished setup
|
||||
Started chan bool
|
||||
// allow tests to tell when the tether has finished
|
||||
Cleaned chan bool
|
||||
// Session exit
|
||||
SessionExit chan bool
|
||||
|
||||
// debug output gets logged here
|
||||
LogBuffer bytes.Buffer
|
||||
|
||||
// session output gets logged here
|
||||
SessionLogBuffer bytes.Buffer
|
||||
|
||||
// the hostname of the system
|
||||
Hostname string
|
||||
// the ip configuration for name index networks
|
||||
IPs map[string]net.IP
|
||||
// filesystem mounts, indexed by disk label
|
||||
Mounts map[string]string
|
||||
|
||||
WindowCol uint32
|
||||
WindowRow uint32
|
||||
Signal ssh.Signal
|
||||
}
|
||||
|
||||
// Start implements the extension method
|
||||
func (t *Mocker) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop implements the extension method
|
||||
func (t *Mocker) Stop() error {
|
||||
close(t.Cleaned)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload implements the extension method
|
||||
func (t *Mocker) Reload(config *tether.ExecutorConfig) error {
|
||||
// the tether has definitely finished it's startup by the time we hit this
|
||||
defer func() {
|
||||
// deal with repeated reloads
|
||||
recover()
|
||||
}()
|
||||
|
||||
close(t.Started)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) Setup(tether.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) Log() (io.Writer, error) {
|
||||
return &t.LogBuffer, nil
|
||||
}
|
||||
|
||||
func (t *Mocker) SessionLog(session *tether.SessionConfig) (dio.DynamicMultiWriter, dio.DynamicMultiWriter, error) {
|
||||
return dio.MultiWriter(&t.SessionLogBuffer), dio.MultiWriter(&t.SessionLogBuffer), nil
|
||||
}
|
||||
|
||||
func (t *Mocker) HandleSessionExit(config *tether.ExecutorConfig, session *tether.SessionConfig) func() {
|
||||
return func() {
|
||||
t.SessionExit <- true
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Mocker) ProcessEnv(env []string) []string {
|
||||
return t.Base.ProcessEnv(env)
|
||||
}
|
||||
|
||||
// SetHostname sets both the kernel hostname and /etc/hostname to the specified string
|
||||
func (t *Mocker) SetHostname(hostname string, aliases ...string) error {
|
||||
defer trace.End(trace.Begin("mocking hostname to " + hostname))
|
||||
|
||||
// TODO: we could mock at a much finer granularity, only extracting the syscall
|
||||
// that would exercise the file modification paths, however it's much less generalizable
|
||||
t.Hostname = hostname
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Mocker) SetupFirewall(cxt context.Context, conf *tether.ExecutorConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply takes the network endpoint configuration and applies it to the system
|
||||
func (t *Mocker) Apply(endpoint *tether.NetworkEndpoint) error {
|
||||
defer trace.End(trace.Begin("mocking endpoint configuration for " + endpoint.Network.Name))
|
||||
t.IPs[endpoint.Network.Name] = endpoint.Assigned.IP
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MountLabel performs a mount with the source treated as a disk label
|
||||
// This assumes that /dev/disk/by-label is being populated, probably by udev
|
||||
func (t *Mocker) MountLabel(ctx context.Context, label, target string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("mocking mounting %s on %s", label, target)))
|
||||
|
||||
if t.Mounts == nil {
|
||||
t.Mounts = make(map[string]string)
|
||||
}
|
||||
|
||||
t.Mounts[label] = target
|
||||
return nil
|
||||
}
|
||||
|
||||
// MountTarget performs a mount with the source treated as an nfs target
|
||||
func (t *Mocker) MountTarget(ctx context.Context, source url.URL, target string, mountOptions string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("mocking mounting %s on %s", source.String(), target)))
|
||||
|
||||
if t.Mounts == nil {
|
||||
t.Mounts = make(map[string]string)
|
||||
}
|
||||
|
||||
t.Mounts[source.String()] = target
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyExistingContent copies the underlying files shadowed by a mount on a directory
|
||||
// to the volume mounted on the directory
|
||||
func (t *Mocker) CopyExistingContent(source string) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("mocking copyExistingContent from %s", source)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fork triggers vmfork and handles the necessary pre/post OS level operations
|
||||
func (t *Mocker) Fork() error {
|
||||
defer trace.End(trace.Begin("mocking fork"))
|
||||
return errors.New("Fork test not implemented")
|
||||
}
|
||||
|
||||
// LaunchUtility uses the underlying implementation for launching and tracking utility processes
|
||||
func (t *Mocker) LaunchUtility(fn tether.UtilityFn) (<-chan int, error) {
|
||||
return t.Base.LaunchUtility(fn)
|
||||
}
|
||||
|
||||
func (t *Mocker) HandleUtilityExit(pid, exitCode int) bool {
|
||||
return t.Base.HandleUtilityExit(pid, exitCode)
|
||||
}
|
||||
|
||||
// TestMain simply so we have control of debugging level and somewhere to call package wide test setup
|
||||
func TestMain(m *testing.M) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
trace.Logger = log.StandardLogger()
|
||||
|
||||
// replace the Sys variable with a mock
|
||||
tether.Sys = system.System{
|
||||
Hosts: &tether.MockHosts{},
|
||||
ResolvConf: &tether.MockResolvConf{},
|
||||
Syscall: &tether.MockSyscall{},
|
||||
Root: os.TempDir(),
|
||||
}
|
||||
|
||||
retCode := m.Run()
|
||||
|
||||
// call with result of m.Run()
|
||||
os.Exit(retCode)
|
||||
}
|
||||
|
||||
func StartTether(t *testing.T, cfg *executor.ExecutorConfig) (tether.Tether, extraconfig.DataSource) {
|
||||
store := extraconfig.New()
|
||||
sink := store.Put
|
||||
src := store.Get
|
||||
extraconfig.Encode(sink, cfg)
|
||||
log.Debugf("Test configuration: %#v", sink)
|
||||
|
||||
tthr = tether.New(src, sink, &Mocked)
|
||||
tthr.Register("mocker", &Mocked)
|
||||
|
||||
// run the tether to service the attach
|
||||
go func() {
|
||||
err := tthr.Start()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return tthr, src
|
||||
}
|
||||
|
||||
func RunTether(t *testing.T, cfg *executor.ExecutorConfig) (tether.Tether, extraconfig.DataSource, error) {
|
||||
store := extraconfig.New()
|
||||
sink := store.Put
|
||||
src := store.Get
|
||||
extraconfig.Encode(sink, cfg)
|
||||
log.Debugf("Test configuration: %#v", sink)
|
||||
|
||||
tthr = tether.New(src, sink, &Mocked)
|
||||
tthr.Register("Mocker", &Mocked)
|
||||
|
||||
// run the tether to service the attach
|
||||
erR := tthr.Start()
|
||||
|
||||
return tthr, src, erR
|
||||
}
|
||||
|
||||
func OptionValueArrayToString(options []types.BaseOptionValue) string {
|
||||
// create the key/value store from the extraconfig slice for lookups
|
||||
kv := make(map[string]string)
|
||||
for i := range options {
|
||||
k := options[i].GetOptionValue().Key
|
||||
v := options[i].GetOptionValue().Value.(string)
|
||||
kv[k] = v
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%#v", kv)
|
||||
}
|
||||
|
||||
func testSetup(t *testing.T) {
|
||||
pc, _, _, _ := runtime.Caller(1)
|
||||
name := runtime.FuncForPC(pc).Name()
|
||||
|
||||
log.Infof("Started test setup for %s", name)
|
||||
|
||||
// use the mock ops - fresh one each time as tests might apply different mocked calls
|
||||
Mocked = Mocker{
|
||||
Started: make(chan bool),
|
||||
Cleaned: make(chan bool),
|
||||
SessionExit: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
func testTeardown(t *testing.T) {
|
||||
// cleanup
|
||||
<-Mocked.Cleaned
|
||||
|
||||
os.RemoveAll(pathPrefix)
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
pc, _, _, _ := runtime.Caller(1)
|
||||
name := runtime.FuncForPC(pc).Name()
|
||||
|
||||
log.Infof("Finished test teardown for %s", name)
|
||||
}
|
||||
260
vendor/github.com/vmware/vic/cmd/vic-init/toolbox.go
generated
vendored
Normal file
260
vendor/github.com/vmware/vic/cmd/vic-init/toolbox.go
generated
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !windows,!darwin
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/toolbox"
|
||||
"github.com/vmware/govmomi/toolbox/vix"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/diag"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// startCommand is the switch for the synthetic commands that are permitted within the appliance.
|
||||
// This is not intended to allow arbitrary commands to be executed.
|
||||
// returns:
|
||||
// pid: toolbox ProcessManager Process id
|
||||
// error
|
||||
func startCommand(m *toolbox.ProcessManager, r *vix.StartProgramRequest) (int64, error) {
|
||||
defer trace.End(trace.Begin(r.ProgramPath))
|
||||
var p *toolbox.Process
|
||||
|
||||
switch r.ProgramPath {
|
||||
case "enable-ssh":
|
||||
p = toolbox.NewProcessFunc(func(ctx context.Context, args string) error {
|
||||
err := enableSSH(args)
|
||||
// #nosec: Errors unhandled.
|
||||
_ = enableShell()
|
||||
return err
|
||||
})
|
||||
case "passwd":
|
||||
p = toolbox.NewProcessFunc(func(ctx context.Context, args string) error {
|
||||
err := passwd(args)
|
||||
// #nosec: Errors unhandled.
|
||||
_ = enableShell()
|
||||
return err
|
||||
})
|
||||
case "test-vc-api":
|
||||
p = toolbox.NewProcessFunc(func(ctx context.Context, args string) error {
|
||||
rc := diag.CheckAPIAvailability(args)
|
||||
if rc == diag.VCStatusOK {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &toolbox.ProcessError{
|
||||
Err: errors.New(diag.UserReadableVCAPITestDescription(rc)),
|
||||
ExitCode: int32(rc),
|
||||
}
|
||||
})
|
||||
default:
|
||||
return -1, os.ErrNotExist
|
||||
}
|
||||
|
||||
return m.Start(r, p)
|
||||
|
||||
}
|
||||
|
||||
// enableShell changes the root shell from /bin/false to /bin/bash
|
||||
// We try to ensure the password is not expired via chage, as chsh
|
||||
// requires an unexpired password to succeed.
|
||||
func enableShell() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// if reset fails, try the rest anyway
|
||||
// #nosec: Errors unhandled.
|
||||
resetPasswdExpiry()
|
||||
|
||||
// #nosec: Subprocess launching should be audited
|
||||
chsh := exec.Command("/bin/chsh", "-s", "/bin/bash", "root")
|
||||
err := chsh.Start()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Failed to launch chsh: %s", err)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// ignore the error - it's likely raced with child reaper, we just want to make sure
|
||||
// that it's exited by the time we pass this point
|
||||
// #nosec: Errors unhandled.
|
||||
chsh.Wait()
|
||||
|
||||
// confirm the change
|
||||
file, err := os.Open("/etc/passwd")
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Failed to open file to confirm change: %s", err)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(file)
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Failed to read line from file to confirm change: %s", err)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// assert that first line is root
|
||||
if !strings.HasPrefix(line, "root") {
|
||||
err := fmt.Errorf("Expected line to start with root: %s", line)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// assert that first line is root
|
||||
if !strings.HasSuffix(line, "/bin/bash\n") {
|
||||
err := fmt.Errorf("Expected line to end with /bin/bash: %s", line)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Attempted to enable the shell for root")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// passwd sets the password for the root user to that provided as an argument
|
||||
func passwd(pass string) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// #nosec: Subprocess launching should be audited
|
||||
setPasswd := exec.Command("/sbin/chpasswd")
|
||||
stdin, err := setPasswd.StdinPipe()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Failed to create stdin pipe for chpasswd: %s", err)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = setPasswd.Start()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Failed to launch chpasswd: %s", err)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stdin.Write([]byte("root:" + pass))
|
||||
|
||||
// so that we're actively waiting when the process exits, or we'll race (and lose) to child reaper
|
||||
go func() {
|
||||
// #nosec: Errors unhandled.
|
||||
setPasswd.Wait()
|
||||
}()
|
||||
|
||||
err = stdin.Close()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Failed to close input to chpasswd: %s", err)
|
||||
log.Error(err)
|
||||
|
||||
// fire and forget as we're already on error path
|
||||
// #nosec: Errors unhandled.
|
||||
setPasswd.Process.Kill()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Attempted to set the password for root")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// enableSSH receives a key as an argument
|
||||
func enableSSH(key string) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// basic sanity check for args - we don't bother validating it's a key
|
||||
if len(key) != 0 {
|
||||
err := os.MkdirAll("/root/.ssh", 0700)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("unable to create path for keys: %s", err)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile("/root/.ssh/authorized_keys", []byte(key), 0600)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("unable to create authorized_keys: %s", err)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return startSSH()
|
||||
}
|
||||
|
||||
// startSSH launches the sshd server
|
||||
func startSSH() error {
|
||||
// #nosec: Subprocess launching should be audited
|
||||
c := exec.Command("/usr/bin/systemctl", "start", "sshd")
|
||||
|
||||
var b bytes.Buffer
|
||||
c.Stdout = &b
|
||||
c.Stderr = &b
|
||||
|
||||
if err := c.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
// because init is explicitly reaping child processes we cannot use simple
|
||||
// exec commands to gather status
|
||||
// #nosec: Errors unhandled.
|
||||
_ = c.Wait()
|
||||
log.Info("Attempted to start ssh service:\n %s", b)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetPasswdExpiry() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// add just enough time for the password not to be expired
|
||||
// if the user wants more time they can actually change the password
|
||||
// This will expire in at most 1 day, perhaps sooner depending on local time
|
||||
// NB: Format is example based - that's the reference time format
|
||||
expireDate := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
|
||||
|
||||
// #nosec: Subprocess launching should be audited
|
||||
chage := exec.Command("/bin/chage", "-M", "1", "-d", expireDate, "root")
|
||||
err := chage.Start()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Failed to launch chage: %s", err)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// ignore the error - it's likely raced with child reaper, we just want to make sure
|
||||
// that it's exited by the time we pass this point
|
||||
// #nosec: Errors unhandled.
|
||||
chage.Wait()
|
||||
|
||||
log.Infof("Attempted reset of password expiry: /bin/chage -M 1 -d %s root", expireDate)
|
||||
return nil
|
||||
}
|
||||
35
vendor/github.com/vmware/vic/cmd/vic-machine-server/Dockerfile
generated
vendored
Normal file
35
vendor/github.com/vmware/vic/cmd/vic-machine-server/Dockerfile
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# Building:
|
||||
# from vic root directory
|
||||
# docker build --no-cache -t vic-machine-server -f cmd/vic-machine-server/Dockerfile .
|
||||
# docker tag vic-machine-server gcr.io/eminent-nation-87317/vic-machine-server:1.x
|
||||
# gcloud auth login
|
||||
# gcloud docker -- push gcr.io/eminent-nation-87317/vic-machine-server:1.x
|
||||
|
||||
FROM vmware/photon:1.0
|
||||
|
||||
RUN set -eux; \
|
||||
tdnf distro-sync --refresh -y; \
|
||||
tdnf info installed; \
|
||||
tdnf clean all
|
||||
|
||||
ENV HOST 0.0.0.0
|
||||
ENV PORT 80
|
||||
ENV TLS_PORT 443
|
||||
|
||||
# Default location for TLS - Specify `-v /host/cert/path:/certs` to use defaults
|
||||
# Override by providing a volume and values for `-e TLS_CERTIFICATE` and `-e TLS_PRIVATE_KEY`
|
||||
ENV TLS_CERTIFICATE=/certs/server.crt
|
||||
ENV TLS_PRIVATE_KEY=/certs/server.key
|
||||
|
||||
EXPOSE $PORT
|
||||
EXPOSE $TLS_PORT
|
||||
|
||||
WORKDIR /opt/vmware/vsphere-integrated-containers/
|
||||
|
||||
COPY bin/vic-machine-server bin/
|
||||
COPY bin/appliance.iso .
|
||||
COPY bin/bootstrap.iso .
|
||||
|
||||
RUN setcap cap_net_bind_service=+ep bin/vic-machine-server
|
||||
|
||||
ENTRYPOINT bin/vic-machine-server
|
||||
89
vendor/github.com/vmware/vic/cmd/vic-machine-server/main.go
generated
vendored
Normal file
89
vendor/github.com/vmware/vic/cmd/vic-machine-server/main.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
|
||||
loads "github.com/go-openapi/loads"
|
||||
flags "github.com/jessevdk/go-flags"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/service/restapi"
|
||||
"github.com/vmware/vic/lib/apiservers/service/restapi/operations"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
|
||||
func main() {
|
||||
|
||||
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
api := operations.NewVicMachineAPI(swaggerSpec)
|
||||
server := restapi.NewServer(api)
|
||||
defer server.Shutdown()
|
||||
|
||||
parser := flags.NewParser(server, flags.Default)
|
||||
parser.ShortDescription = "vic-machine API"
|
||||
parser.LongDescription = "An API for interacting with vic-machine as a RESTful web service."
|
||||
|
||||
server.ConfigureFlags()
|
||||
for _, optsGroup := range api.CommandLineOptionsGroups {
|
||||
_, err := parser.AddGroup(optsGroup.ShortDescription, optsGroup.LongDescription, optsGroup.Options)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
var pprofFlags = struct {
|
||||
Host string `long:"pprof-host" description:"the IP to listen on for pprof connections" default:"localhost" env:"PPROF_HOST"`
|
||||
Port int `long:"pprof-port" description:"the port to listen on for pprof connections" default:"6060" env:"PPROF_PORT"`
|
||||
}{}
|
||||
_, err = parser.AddGroup("Profiling Options", "", &pprofFlags)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if _, err := parser.Parse(); err != nil {
|
||||
code := 1
|
||||
if fe, ok := err.(*flags.Error); ok {
|
||||
if fe.Type == flags.ErrHelp {
|
||||
code = 0
|
||||
}
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
server.ConfigureAPI()
|
||||
|
||||
go func() {
|
||||
addr := fmt.Sprintf("%s:%d", pprofFlags.Host, pprofFlags.Port)
|
||||
trace.Logger.Infof("Serving pprof at http://%s", addr)
|
||||
trace.Logger.Info(http.ListenAndServe(addr, nil))
|
||||
trace.Logger.Infof("Stopped serving pprof at http://%s", addr)
|
||||
}()
|
||||
|
||||
if err := server.Serve(); err != nil {
|
||||
trace.Logger.Error(err)
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
466
vendor/github.com/vmware/vic/cmd/vic-machine/common/certificate.go
generated
vendored
Normal file
466
vendor/github.com/vmware/vic/cmd/vic-machine/common/certificate.go
generated
vendored
Normal file
@@ -0,0 +1,466 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/pkg/certificate"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// CertFactory has all input parameters for vic-machine certificate commands needed to create a certificate
|
||||
type CertFactory struct {
|
||||
Networks
|
||||
|
||||
CertPath string
|
||||
DisplayName string
|
||||
Scert string
|
||||
Skey string
|
||||
Ccert string
|
||||
Ckey string
|
||||
Cacert string
|
||||
Cakey string
|
||||
ClientCert *tls.Certificate
|
||||
ClientCAsArg cli.StringSlice `arg:"tls-ca"`
|
||||
ClientCAs []byte
|
||||
EnvFile string
|
||||
Cname string
|
||||
Org cli.StringSlice
|
||||
KeySize int
|
||||
NoTLS bool
|
||||
NoTLSverify bool
|
||||
KeyPEM []byte
|
||||
CertPEM []byte
|
||||
NoSaveToDisk bool
|
||||
}
|
||||
|
||||
func (c *CertFactory) CertFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "tls-server-key",
|
||||
Value: "",
|
||||
Usage: "Virtual Container Host private key file (server certificate)",
|
||||
Destination: &c.Skey,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tls-server-cert",
|
||||
Value: "",
|
||||
Usage: "Virtual Container Host x509 certificate file (server certificate)",
|
||||
Destination: &c.Scert,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tls-cname",
|
||||
Value: "",
|
||||
Usage: "Common Name to use in generated CA certificate when requiring client certificate authentication",
|
||||
Destination: &c.Cname,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tls-cert-path",
|
||||
Value: "",
|
||||
Usage: "The path to check for existing certificates and in which to save generated certificates. Defaults to './<vch name>/'",
|
||||
Destination: &c.CertPath,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-tlsverify, kv",
|
||||
Usage: "Disable authentication via client certificates - for more tls options see advanced help (-x)",
|
||||
Destination: &c.NoTLSverify,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "organization",
|
||||
Usage: "A list of identifiers to record in the generated certificates. Defaults to VCH name and IP/FQDN if not provided.",
|
||||
Value: &c.Org,
|
||||
Hidden: true,
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "certificate-key-size, ksz",
|
||||
Usage: "Size of key to use when generating certificates",
|
||||
Value: 2048,
|
||||
Destination: &c.KeySize,
|
||||
Hidden: true,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "tls-ca, ca",
|
||||
Usage: "Specify a list of certificate authority files to use for client verification",
|
||||
Value: &c.ClientCAsArg,
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CertFactory) ProcessCertificates(op trace.Operation, displayName string, force bool, debug int) error {
|
||||
// set up the locations for the certificates and env file
|
||||
if c.CertPath == "" {
|
||||
c.CertPath = displayName
|
||||
}
|
||||
c.EnvFile = fmt.Sprintf("%s/%s.env", c.CertPath, displayName)
|
||||
|
||||
// check for insecure case
|
||||
if c.NoTLS {
|
||||
op.Warn("Configuring without TLS - all communications will be insecure")
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Scert != "" && c.Skey == "" {
|
||||
return cli.NewExitError("key and cert should be specified at the same time", 1)
|
||||
}
|
||||
if c.Scert == "" && c.Skey != "" {
|
||||
return cli.NewExitError("key and cert should be specified at the same time", 1)
|
||||
}
|
||||
|
||||
// if we've not got a specific CommonName but do have a static IP then go with that.
|
||||
if c.Cname == "" {
|
||||
if c.ClientNetworkIP != "" {
|
||||
c.Cname = c.ClientNetworkIP
|
||||
op.Infof("Using client-network-ip as cname where needed - use --tls-cname to override: %s", c.Cname)
|
||||
} else if c.PublicNetworkIP != "" && (c.PublicNetworkName == c.ClientNetworkName || c.ClientNetworkName == "") {
|
||||
c.Cname = c.PublicNetworkIP
|
||||
op.Infof("Using public-network-ip as cname where needed - use --tls-cname to override: %s", c.Cname)
|
||||
} else if c.ManagementNetworkIP != "" && (c.ManagementNetworkName == c.ClientNetworkName || (c.ClientNetworkName == "" && c.ManagementNetworkName == c.PublicNetworkName)) {
|
||||
c.Cname = c.ManagementNetworkIP
|
||||
op.Infof("Using management-network-ip as cname where needed - use --tls-cname to override: %s", c.Cname)
|
||||
}
|
||||
|
||||
if c.Cname != "" {
|
||||
// Strip network mask from IP address if set.
|
||||
// #nosec: Errors unhandled.
|
||||
if cnameIP, _, _ := net.ParseCIDR(c.Cname); cnameIP != nil {
|
||||
c.Cname = cnameIP.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load what certificates we can
|
||||
cas, keypair, err := c.loadCertificates(op, debug)
|
||||
if err != nil {
|
||||
op.Errorf("Unable to load certificates: %s", err)
|
||||
if !force {
|
||||
return err
|
||||
}
|
||||
|
||||
op.Warnf("Ignoring error loading certificates due to --force")
|
||||
cas = nil
|
||||
keypair = nil
|
||||
err = nil
|
||||
}
|
||||
|
||||
// we need to generate some part of the certificate configuration
|
||||
gcas, gkeypair, err := c.generateCertificates(op, keypair == nil, !c.NoTLSverify && len(cas) == 0)
|
||||
if err != nil {
|
||||
op.Error("cannot continue: unable to generate certificates")
|
||||
return err
|
||||
}
|
||||
|
||||
if keypair != nil {
|
||||
c.KeyPEM = keypair.KeyPEM
|
||||
c.CertPEM = keypair.CertPEM
|
||||
} else if gkeypair != nil {
|
||||
c.KeyPEM = gkeypair.KeyPEM
|
||||
c.CertPEM = gkeypair.CertPEM
|
||||
}
|
||||
|
||||
if len(cas) == 0 {
|
||||
cas = gcas
|
||||
}
|
||||
|
||||
if len(c.KeyPEM) == 0 {
|
||||
return errors.New("Failed to load or generate server certificates")
|
||||
}
|
||||
|
||||
if len(cas) == 0 && !c.NoTLSverify {
|
||||
return errors.New("Failed to load or generate certificate authority")
|
||||
}
|
||||
|
||||
// do we have key, cert, and --no-tlsverify
|
||||
if c.NoTLSverify || len(cas) == 0 {
|
||||
op.Warnf("Configuring without TLS verify - certificate-based authentication disabled")
|
||||
return nil
|
||||
}
|
||||
|
||||
c.ClientCAs = cas
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadCertificates returns the client CA pool and the keypair for server certificates on success
|
||||
func (c *CertFactory) loadCertificates(op trace.Operation, debug int) ([]byte, *certificate.KeyPair, error) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
// reads each of the files specified, assuming that they are PEM encoded certs,
|
||||
// and constructs a byte array suitable for passing to CertPool.AppendCertsFromPEM
|
||||
var certs []byte
|
||||
for _, f := range c.ClientCAsArg {
|
||||
b, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
err = errors.Errorf("Failed to load authority from file %s: %s", f, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
certs = append(certs, b...)
|
||||
op.Infof("Loaded CA from %s", f)
|
||||
}
|
||||
|
||||
var keypair *certificate.KeyPair
|
||||
// default names
|
||||
skey := filepath.Join(c.CertPath, certificate.ServerKey)
|
||||
scert := filepath.Join(c.CertPath, certificate.ServerCert)
|
||||
ca := filepath.Join(c.CertPath, certificate.CACert)
|
||||
ckey := filepath.Join(c.CertPath, certificate.ClientKey)
|
||||
ccert := filepath.Join(c.CertPath, certificate.ClientCert)
|
||||
|
||||
// if specific files are supplied, use those
|
||||
explicit := false
|
||||
if c.Scert != "" && c.Skey != "" {
|
||||
explicit = true
|
||||
skey = c.Skey
|
||||
scert = c.Scert
|
||||
}
|
||||
|
||||
// load the server certificate
|
||||
keypair = certificate.NewKeyPair(scert, skey, nil, nil)
|
||||
if err := keypair.LoadCertificate(); err != nil {
|
||||
if explicit || !os.IsNotExist(err) {
|
||||
// if these files were explicit paths, or anything other than not found, fail
|
||||
op.Errorf("Failed to load certificate: %s", err)
|
||||
return certs, nil, err
|
||||
}
|
||||
|
||||
op.Debugf("Unable to locate existing server certificate in cert path")
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// check that any supplied cname matches the server cert CN
|
||||
cert, err := keypair.Certificate()
|
||||
if err != nil {
|
||||
op.Errorf("Failed to parse certificate: %s", err)
|
||||
return certs, nil, err
|
||||
}
|
||||
|
||||
if cert.Leaf == nil {
|
||||
op.Warnf("Failed to load x509 leaf: Unable to confirm server certificate cname matches provided cname %q. Continuing...", c.Cname)
|
||||
} else {
|
||||
// We just do a direct equality check here - trying to be clever is liable to lead to hard
|
||||
// to diagnose errors
|
||||
if cert.Leaf.Subject.CommonName != c.Cname {
|
||||
op.Errorf("Provided cname does not match that in existing server certificate: %s", cert.Leaf.Subject.CommonName)
|
||||
if debug > 2 {
|
||||
op.Debugf("Certificate does not match provided cname: %#+v", cert.Leaf)
|
||||
}
|
||||
return certs, nil, fmt.Errorf("cname option doesn't match existing server certificate in certificate path %s", c.CertPath)
|
||||
}
|
||||
}
|
||||
|
||||
op.Infof("Loaded server certificate %s", scert)
|
||||
c.Skey = skey
|
||||
c.Scert = scert
|
||||
|
||||
// only try for CA certificate if no-tlsverify has NOT been specified and we haven't already loaded an authority cert
|
||||
if !c.NoTLSverify && len(certs) == 0 {
|
||||
b, err := ioutil.ReadFile(ca)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
op.Debugf("Unable to locate existing CA in cert path")
|
||||
return certs, keypair, nil
|
||||
}
|
||||
|
||||
// if the CA exists but cannot be loaded then it's an error
|
||||
op.Errorf("Failed to load authority from certificate path %s: %s", c.CertPath, err)
|
||||
return certs, keypair, errors.New("failed to load certificate authority")
|
||||
}
|
||||
|
||||
c.Cacert = ca
|
||||
|
||||
op.Infof("Loaded CA with default name from certificate path %s", c.CertPath)
|
||||
certs = b
|
||||
|
||||
// load client certs - we ensure the client certs validate with the provided CA or ignore any we find
|
||||
cpair := certificate.NewKeyPair(ccert, ckey, nil, nil)
|
||||
if err := cpair.LoadCertificate(); err != nil {
|
||||
op.Warnf("Unable to load client certificate - validation of API endpoint will be best effort only: %s", err)
|
||||
}
|
||||
|
||||
clientCert, err := certificate.VerifyClientCert(certs, cpair)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case certificate.CertParseError, certificate.CreateCAPoolError:
|
||||
op.Debug(err)
|
||||
case certificate.CertVerifyError:
|
||||
op.Warnf("%s - continuing without client certificate", err)
|
||||
}
|
||||
|
||||
return certs, keypair, nil
|
||||
}
|
||||
|
||||
c.Ckey = ckey
|
||||
c.Ccert = ccert
|
||||
c.ClientCert = clientCert
|
||||
|
||||
op.Infof("Loaded client certificate with default name from certificate path %s", c.CertPath)
|
||||
}
|
||||
|
||||
return certs, keypair, nil
|
||||
}
|
||||
|
||||
func (c *CertFactory) generateCertificates(op trace.Operation, server bool, client bool) ([]byte, *certificate.KeyPair, error) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if !server && !client {
|
||||
op.Debug("Not generating server or client certs, nothing for generateCertificates to do")
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
var certs []byte
|
||||
// generate the certs and keys with names conforming the default the docker client expects
|
||||
files, err := ioutil.ReadDir(c.CertPath)
|
||||
if len(files) > 0 {
|
||||
return nil, nil, fmt.Errorf("Specified directory to store certificates is not empty. Specify a new path in which to store generated certificates using --tls-cert-path or remove the contents of \"%s\" and run vic-machine again.", c.CertPath)
|
||||
}
|
||||
|
||||
if !c.NoSaveToDisk {
|
||||
err = os.MkdirAll(c.CertPath, 0700)
|
||||
if err != nil {
|
||||
op.Errorf("Unable to make directory \"%s\" to hold certificates (set via --tls-cert-path)", c.CertPath)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
c.Skey = filepath.Join(c.CertPath, certificate.ServerKey)
|
||||
c.Scert = filepath.Join(c.CertPath, certificate.ServerCert)
|
||||
|
||||
c.Ckey = filepath.Join(c.CertPath, certificate.ClientKey)
|
||||
c.Ccert = filepath.Join(c.CertPath, certificate.ClientCert)
|
||||
|
||||
cakey := filepath.Join(c.CertPath, certificate.CAKey)
|
||||
c.Cacert = filepath.Join(c.CertPath, certificate.CACert)
|
||||
|
||||
if server && !client {
|
||||
op.Infof("Generating self-signed certificate/key pair - private key in %s", c.Skey)
|
||||
keypair := certificate.NewKeyPair(c.Scert, c.Skey, nil, nil)
|
||||
err := keypair.CreateSelfSigned(c.Cname, nil, c.KeySize)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to generate self-signed certificate: %s", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
if !c.NoSaveToDisk {
|
||||
if err = keypair.SaveCertificate(); err != nil {
|
||||
op.Errorf("Failed to save server certificates: %s", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return certs, keypair, nil
|
||||
}
|
||||
|
||||
// client auth path
|
||||
if c.Cname == "" {
|
||||
op.Error("Common Name must be provided when generating certificates for client authentication:")
|
||||
op.Info(" --tls-cname=<FQDN or static IP> # for the appliance VM")
|
||||
op.Info(" --tls-cname=<*.yourdomain.com> # if DNS has entries in that form for DHCP addresses (less secure)")
|
||||
op.Info(" --no-tlsverify # disables client authentication (anyone can connect to the VCH)")
|
||||
op.Info(" --no-tls # disables TLS entirely")
|
||||
op.Info("")
|
||||
|
||||
return certs, nil, errors.New("provide Common Name for server certificate")
|
||||
}
|
||||
|
||||
// for now re-use the display name as the organisation if unspecified
|
||||
if len(c.Org) == 0 {
|
||||
c.Org = []string{c.DisplayName}
|
||||
}
|
||||
if len(c.Org) == 1 && !strings.HasPrefix(c.Cname, "*") {
|
||||
// Add in the cname if it's not a wildcard
|
||||
c.Org = append(c.Org, c.Cname)
|
||||
}
|
||||
|
||||
// Certificate authority
|
||||
op.Infof("Generating CA certificate/key pair - private key in %s", cakey)
|
||||
cakp := certificate.NewKeyPair(c.Cacert, cakey, nil, nil)
|
||||
err = cakp.CreateRootCA(c.Cname, c.Org, c.KeySize)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to generate CA: %s", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
if !c.NoSaveToDisk {
|
||||
if err = cakp.SaveCertificate(); err != nil {
|
||||
op.Errorf("Failed to save CA certificates: %s", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Server certificates
|
||||
var skp *certificate.KeyPair
|
||||
if server {
|
||||
op.Infof("Generating server certificate/key pair - private key in %s", c.Skey)
|
||||
skp = certificate.NewKeyPair(c.Scert, c.Skey, nil, nil)
|
||||
err = skp.CreateServerCertificate(c.Cname, c.Org, c.KeySize, cakp)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to generate server certificates: %s", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
if !c.NoSaveToDisk {
|
||||
if err = skp.SaveCertificate(); err != nil {
|
||||
op.Errorf("Failed to save server certificates: %s", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Client certificates
|
||||
if client {
|
||||
op.Infof("Generating client certificate/key pair - private key in %s", c.Ckey)
|
||||
ckp := certificate.NewKeyPair(c.Ccert, c.Ckey, nil, nil)
|
||||
err = ckp.CreateClientCertificate(c.Cname, c.Org, c.KeySize, cakp)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to generate client certificates: %s", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
if !c.NoSaveToDisk {
|
||||
if err = ckp.SaveCertificate(); err != nil {
|
||||
op.Errorf("Failed to save client certificates: %s", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
c.ClientCert, err = ckp.Certificate()
|
||||
if err != nil {
|
||||
op.Warnf("Failed to stash client certificate for later application level validation: %s", err)
|
||||
}
|
||||
|
||||
// If openssl is present, try to generate a browser friendly pfx file (a bundle of the public certificate AND the private key)
|
||||
// The pfx file can be imported directly into keychains for client certificate authentication
|
||||
certPath := filepath.Clean(c.CertPath)
|
||||
args := strings.Split(fmt.Sprintf("pkcs12 -export -out %[1]s/cert.pfx -inkey %[1]s/key.pem -in %[1]s/cert.pem -certfile %[1]s/ca.pem -password pass:", certPath), " ")
|
||||
// #nosec: Subprocess launching with variable
|
||||
pfx := exec.Command("openssl", args...)
|
||||
out, err := pfx.CombinedOutput()
|
||||
if err != nil {
|
||||
op.Debug(out)
|
||||
op.Warnf("Failed to generate browser friendly PFX client certificate: %s", err)
|
||||
} else {
|
||||
op.Infof("Generated browser friendly PFX client certificate - certificate in %s/cert.pfx", certPath)
|
||||
}
|
||||
}
|
||||
|
||||
return cakp.CertPEM, skp, nil
|
||||
}
|
||||
60
vendor/github.com/vmware/vic/cmd/vic-machine/common/certificate_test.go
generated
vendored
Normal file
60
vendor/github.com/vmware/vic/cmd/vic-machine/common/certificate_test.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
cs = &CertFactory{}
|
||||
)
|
||||
|
||||
func TestGenKey(t *testing.T) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
os.Args = []string{"cmd", "create"}
|
||||
flag.Parse()
|
||||
cs.NoTLS = false
|
||||
cs.CertPath = "install-test"
|
||||
cs.Cname = "common name"
|
||||
cs.KeySize = 1024
|
||||
|
||||
op := trace.NewOperation(context.Background(), "TestGenKey")
|
||||
|
||||
ca, kp, err := cs.generateCertificates(op, true, true)
|
||||
defer os.RemoveAll(fmt.Sprintf("./%s", cs.CertPath))
|
||||
|
||||
assert.NoError(t, err, "Expected to cleanly generate certificates")
|
||||
assert.NotEmpty(t, ca, "Expected CA to contain data")
|
||||
assert.NotNil(t, kp, "Expected keypair to contain data")
|
||||
assert.NotEmpty(t, kp.CertPEM, "Expected certificate to contain data")
|
||||
assert.NotEmpty(t, kp.CertPEM, "Expected key to contain data")
|
||||
|
||||
ca, kp, err = cs.loadCertificates(op, 2)
|
||||
assert.NoError(t, err, "Expected to cleanly load certificates")
|
||||
assert.NotEmpty(t, ca, "Expected CA to contain data")
|
||||
assert.NotNil(t, kp, "Expected keypair to contain data")
|
||||
assert.NotEmpty(t, kp.CertPEM, "Expected certificate to contain data")
|
||||
assert.NotEmpty(t, kp.CertPEM, "Expected key to contain data")
|
||||
}
|
||||
317
vendor/github.com/vmware/vic/cmd/vic-machine/common/cnetworks.go
generated
vendored
Normal file
317
vendor/github.com/vmware/vic/cmd/vic-machine/common/cnetworks.go
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/pkg/ip"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// CNetworks holds user input from container network flags
|
||||
type CNetworks struct {
|
||||
ContainerNetworks cli.StringSlice `arg:"container-network"`
|
||||
ContainerNetworksGateway cli.StringSlice `arg:"container-network-gateway"`
|
||||
ContainerNetworksIPRanges cli.StringSlice `arg:"container-network-ip-range"`
|
||||
ContainerNetworksDNS cli.StringSlice `arg:"container-network-dns"`
|
||||
ContainerNetworksFirewall cli.StringSlice `arg:"container-network-firewall"`
|
||||
IsSet bool
|
||||
}
|
||||
|
||||
// ContainerNetworks holds container network data after processing
|
||||
type ContainerNetworks struct {
|
||||
MappedNetworks map[string]string `cmd:"parent" label:"key-value"`
|
||||
MappedNetworksGateways map[string]net.IPNet `cmd:"gateway" label:"key-value"`
|
||||
MappedNetworksIPRanges map[string][]ip.Range `cmd:"ip-range" label:"key-value"`
|
||||
MappedNetworksDNS map[string][]net.IP `cmd:"dns" label:"key-value"`
|
||||
MappedNetworksFirewalls map[string]executor.TrustLevel `cmd:"firewall" label:"key-value"`
|
||||
}
|
||||
|
||||
func (c *ContainerNetworks) IsSet() bool {
|
||||
return len(c.MappedNetworks) > 0 ||
|
||||
len(c.MappedNetworksGateways) > 0 ||
|
||||
len(c.MappedNetworksIPRanges) > 0 ||
|
||||
len(c.MappedNetworksDNS) > 0 ||
|
||||
len(c.MappedNetworksFirewalls) > 0
|
||||
}
|
||||
|
||||
func (c *CNetworks) CNetworkFlags(hidden bool) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "container-network, cn",
|
||||
Value: &c.ContainerNetworks,
|
||||
Usage: "vSphere network list that containers can use directly with labels, e.g. vsphere-net:backend. Defaults to DCHP - see advanced help (-x).",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "container-network-gateway, cng",
|
||||
Value: &c.ContainerNetworksGateway,
|
||||
Usage: "Gateway for the container network's subnet in CONTAINER-NETWORK:SUBNET format, e.g. vsphere-net:172.16.0.1/16",
|
||||
Hidden: hidden,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "container-network-ip-range, cnr",
|
||||
Value: &c.ContainerNetworksIPRanges,
|
||||
Usage: "IP range for the container network in CONTAINER-NETWORK:IP-RANGE format, e.g. vsphere-net:172.16.0.0/24, vsphere-net:172.16.0.10-172.16.0.20",
|
||||
Hidden: hidden,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "container-network-dns, cnd",
|
||||
Value: &c.ContainerNetworksDNS,
|
||||
Usage: "DNS servers for the container network in CONTAINER-NETWORK:DNS format, e.g. vsphere-net:8.8.8.8. Ignored if no static IP assigned.",
|
||||
Hidden: hidden,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "container-network-firewall, cnf",
|
||||
Value: &c.ContainerNetworksFirewall,
|
||||
Usage: "Container network trust level in CONTAINER-NETWORK:LEVEL format. Options: Closed, Outbound, Peers, Published, Open.",
|
||||
Hidden: hidden,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func parseContainerNetworkGateways(cgs []string) (map[string]net.IPNet, error) {
|
||||
gws := make(map[string]net.IPNet)
|
||||
for _, cg := range cgs {
|
||||
m := &ipNetUnmarshaler{}
|
||||
vnet, err := parseVnetParam(cg, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := gws[vnet]; ok {
|
||||
return nil, fmt.Errorf("Duplicate gateway specified for container network %s", vnet)
|
||||
}
|
||||
|
||||
gws[vnet] = net.IPNet{IP: m.ip, Mask: m.ipnet.Mask}
|
||||
}
|
||||
|
||||
return gws, nil
|
||||
}
|
||||
|
||||
func parseContainerNetworkIPRanges(cps []string) (map[string][]ip.Range, error) {
|
||||
pools := make(map[string][]ip.Range)
|
||||
for _, cp := range cps {
|
||||
ipr := &ip.Range{}
|
||||
vnet, err := parseVnetParam(cp, ipr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pools[vnet] = append(pools[vnet], *ipr)
|
||||
}
|
||||
|
||||
return pools, nil
|
||||
}
|
||||
|
||||
func parseContainerNetworkDNS(cds []string) (map[string][]net.IP, error) {
|
||||
dns := make(map[string][]net.IP)
|
||||
for _, cd := range cds {
|
||||
var ip net.IP
|
||||
vnet, err := parseVnetParam(cd, &ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("DNS IP not specified for container network %s", vnet)
|
||||
}
|
||||
|
||||
dns[vnet] = append(dns[vnet], ip)
|
||||
}
|
||||
|
||||
return dns, nil
|
||||
}
|
||||
|
||||
func parseContainerNetworkFirewalls(cfs []string) (map[string]executor.TrustLevel, error) {
|
||||
firewalls := make(map[string]executor.TrustLevel)
|
||||
for _, cf := range cfs {
|
||||
vnet, value, err := splitVnetParam(cf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing container network parameter %s: %s", cf, err)
|
||||
}
|
||||
trust, err := executor.ParseTrustLevel(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
firewalls[vnet] = trust
|
||||
}
|
||||
return firewalls, nil
|
||||
}
|
||||
|
||||
func splitVnetParam(p string) (vnet string, value string, err error) {
|
||||
mapped := strings.Split(p, ":")
|
||||
if len(mapped) == 0 || len(mapped) > 2 {
|
||||
err = fmt.Errorf("Invalid value for parameter %s", p)
|
||||
return
|
||||
}
|
||||
|
||||
vnet = mapped[0]
|
||||
if vnet == "" {
|
||||
err = fmt.Errorf("Container network not specified in parameter %s", p)
|
||||
return
|
||||
}
|
||||
|
||||
// If the supplied vSphere network contains spaces then the user must supply a network alias. Guest info won't receive a name with spaces.
|
||||
if strings.Contains(vnet, " ") && (len(mapped) == 1 || (len(mapped) == 2 && len(mapped[1]) == 0)) {
|
||||
err = fmt.Errorf("A network alias must be supplied when network name %q contains spaces.", p)
|
||||
return
|
||||
}
|
||||
|
||||
if len(mapped) > 1 {
|
||||
// Make sure the alias does not contain spaces
|
||||
if strings.Contains(mapped[1], " ") {
|
||||
err = fmt.Errorf("The network alias supplied in %q cannot contain spaces.", p)
|
||||
return
|
||||
}
|
||||
value = mapped[1]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parseVnetParam(p string, m encoding.TextUnmarshaler) (vnet string, err error) {
|
||||
vnet, v, err := splitVnetParam(p)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error parsing container network parameter %s: %s", p, err)
|
||||
}
|
||||
|
||||
if err = m.UnmarshalText([]byte(v)); err != nil {
|
||||
return "", fmt.Errorf("Error parsing container network parameter %s: %s", p, err)
|
||||
}
|
||||
|
||||
return vnet, nil
|
||||
}
|
||||
|
||||
type ipNetUnmarshaler struct {
|
||||
ipnet *net.IPNet
|
||||
ip net.IP
|
||||
}
|
||||
|
||||
func (m *ipNetUnmarshaler) UnmarshalText(text []byte) error {
|
||||
s := string(text)
|
||||
ip, ipnet, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.ipnet = ipnet
|
||||
m.ip = ip
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessContainerNetworks parses container network settings and returns a
|
||||
// struct containing all processed container network fields on success.
|
||||
func (c *CNetworks) ProcessContainerNetworks(op trace.Operation) (ContainerNetworks, error) {
|
||||
cNetworks := ContainerNetworks{
|
||||
MappedNetworks: make(map[string]string),
|
||||
MappedNetworksGateways: make(map[string]net.IPNet),
|
||||
MappedNetworksIPRanges: make(map[string][]ip.Range),
|
||||
MappedNetworksDNS: make(map[string][]net.IP),
|
||||
MappedNetworksFirewalls: make(map[string]executor.TrustLevel),
|
||||
}
|
||||
|
||||
if c.ContainerNetworks != nil || c.ContainerNetworksGateway != nil ||
|
||||
c.ContainerNetworksIPRanges != nil || c.ContainerNetworksDNS != nil {
|
||||
c.IsSet = true
|
||||
}
|
||||
|
||||
gws, err := parseContainerNetworkGateways([]string(c.ContainerNetworksGateway))
|
||||
if err != nil {
|
||||
return cNetworks, cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
|
||||
pools, err := parseContainerNetworkIPRanges([]string(c.ContainerNetworksIPRanges))
|
||||
if err != nil {
|
||||
return cNetworks, cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
|
||||
dns, err := parseContainerNetworkDNS([]string(c.ContainerNetworksDNS))
|
||||
if err != nil {
|
||||
return cNetworks, cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
|
||||
firewalls, err := parseContainerNetworkFirewalls([]string(c.ContainerNetworksFirewall))
|
||||
if err != nil {
|
||||
return cNetworks, cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
|
||||
// Parse container networks
|
||||
for _, cn := range c.ContainerNetworks {
|
||||
vnet, v, err := splitVnetParam(cn)
|
||||
if err != nil {
|
||||
return cNetworks, cli.NewExitError(err.Error(), 1)
|
||||
}
|
||||
|
||||
alias := vnet
|
||||
if v != "" {
|
||||
alias = v
|
||||
}
|
||||
|
||||
cNetworks.MappedNetworks[alias] = vnet
|
||||
cNetworks.MappedNetworksGateways[alias] = gws[vnet]
|
||||
cNetworks.MappedNetworksIPRanges[alias] = pools[vnet]
|
||||
cNetworks.MappedNetworksDNS[alias] = dns[vnet]
|
||||
cNetworks.MappedNetworksFirewalls[alias] = firewalls[vnet]
|
||||
|
||||
delete(gws, vnet)
|
||||
delete(pools, vnet)
|
||||
delete(dns, vnet)
|
||||
delete(firewalls, vnet)
|
||||
}
|
||||
|
||||
var hasError bool
|
||||
fmtMsg := "The following container network %s is set, but CONTAINER-NETWORK cannot be found. Please check the --container-network and %s settings"
|
||||
if len(gws) > 0 {
|
||||
op.Errorf(fmtMsg, "gateway", "--container-network-gateway")
|
||||
for key, value := range gws {
|
||||
mask, _ := value.Mask.Size()
|
||||
op.Errorf("\t%s:%s/%d, %q should be a vSphere network name", key, value.IP, mask, key)
|
||||
}
|
||||
hasError = true
|
||||
}
|
||||
if len(pools) > 0 {
|
||||
op.Errorf(fmtMsg, "ip range", "--container-network-ip-range")
|
||||
for key, value := range pools {
|
||||
op.Errorf("\t%s:%s, %q should be a vSphere network name", key, value, key)
|
||||
}
|
||||
hasError = true
|
||||
}
|
||||
if len(dns) > 0 {
|
||||
op.Errorf(fmtMsg, "dns", "--container-network-dns")
|
||||
for key, value := range dns {
|
||||
op.Errorf("\t%s:%s, %q should be a vSphere network name", key, value, key)
|
||||
}
|
||||
hasError = true
|
||||
}
|
||||
if len(firewalls) > 0 {
|
||||
op.Errorf(fmtMsg, "firewall", "--container-network-firewall")
|
||||
for key := range firewalls {
|
||||
op.Errorf("\t%q should be a vSphere network name", key)
|
||||
}
|
||||
hasError = true
|
||||
}
|
||||
if hasError {
|
||||
return cNetworks, cli.NewExitError("Inconsistent container network configuration.", 1)
|
||||
}
|
||||
|
||||
return cNetworks, nil
|
||||
}
|
||||
211
vendor/github.com/vmware/vic/cmd/vic-machine/common/cnetworks_test.go
generated
vendored
Normal file
211
vendor/github.com/vmware/vic/cmd/vic-machine/common/cnetworks_test.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/vic/pkg/ip"
|
||||
)
|
||||
|
||||
func TestParseContainerNetworkGateways(t *testing.T) {
|
||||
var tests = []struct {
|
||||
cgs []string
|
||||
gws map[string]net.IPNet
|
||||
err error
|
||||
}{
|
||||
{[]string{""}, nil, fmt.Errorf("")},
|
||||
{[]string{"foo:"}, nil, fmt.Errorf("")},
|
||||
{[]string{":"}, nil, fmt.Errorf("")},
|
||||
{[]string{":10.10.10.10/24"}, nil, fmt.Errorf("")},
|
||||
{[]string{":foo"}, nil, fmt.Errorf("")},
|
||||
{[]string{"foo:10"}, nil, fmt.Errorf("")},
|
||||
{[]string{"foo:10.10.10.10/24", "foo:10.10.10.2/24"}, nil, fmt.Errorf("")},
|
||||
{
|
||||
[]string{"foo:10.10.10.10/24", "bar:10.10.9.1/16"},
|
||||
map[string]net.IPNet{
|
||||
"foo": {IP: net.ParseIP("10.10.10.10"), Mask: net.CIDRMask(24, 32)},
|
||||
"bar": {IP: net.ParseIP("10.10.9.1"), Mask: net.CIDRMask(16, 32)},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
gws, err := parseContainerNetworkGateways(te.cgs)
|
||||
if te.err != nil {
|
||||
if err == nil {
|
||||
t.Fatalf("parseContainerNetworkGateways(%s) => (%v, nil) want (nil, err)", te.cgs, gws)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err != te.err ||
|
||||
gws == nil ||
|
||||
len(gws) != len(te.gws) {
|
||||
t.Fatalf("parseContainerNetworkGateways(%s) => (%v, %s) want (%v, nil)", te.cgs, gws, err, te.gws)
|
||||
}
|
||||
|
||||
for v, g := range gws {
|
||||
if g2, ok := te.gws[v]; !ok {
|
||||
t.Fatalf("parseContainerNetworkGateways(%s) => (%v, %s) want (%v, nil)", te.cgs, gws, err, te.gws)
|
||||
} else if !g2.IP.Equal(g.IP) || bytes.Compare(g2.Mask, g.Mask) != 0 {
|
||||
t.Fatalf("parseContainerNetworkGateways(%s) => (%v, %s) want (%v, nil)", te.cgs, gws, err, te.gws)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseContainerNetworkIPRanges(t *testing.T) {
|
||||
var tests = []struct {
|
||||
cps []string
|
||||
iprs map[string][]*ip.Range
|
||||
err error
|
||||
}{
|
||||
{[]string{""}, nil, fmt.Errorf("")},
|
||||
{[]string{"foo:"}, nil, fmt.Errorf("")},
|
||||
{[]string{":"}, nil, fmt.Errorf("")},
|
||||
{[]string{":10.10.10.10-24"}, nil, fmt.Errorf("")},
|
||||
{[]string{":foo"}, nil, fmt.Errorf("")},
|
||||
{[]string{"foo:10"}, nil, fmt.Errorf("")},
|
||||
{[]string{"foo:10.10.10.10-9"}, nil, fmt.Errorf("")},
|
||||
{[]string{"foo:10.10.10.10-10.10.10.9"}, nil, fmt.Errorf("")},
|
||||
{
|
||||
[]string{"foo:10.10.10.10-24"},
|
||||
map[string][]*ip.Range{"foo": {ip.NewRange(net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24"))}}, nil},
|
||||
{
|
||||
[]string{"foo:10.10.10.10-10.10.10.24"},
|
||||
map[string][]*ip.Range{"foo": {ip.NewRange(net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24"))}},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]string{"foo:10.10.10.10-10.10.10.24", "foo:10.10.10.100-10.10.10.105"},
|
||||
map[string][]*ip.Range{
|
||||
"foo": {
|
||||
ip.NewRange(net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24")),
|
||||
ip.NewRange(net.ParseIP("10.10.10.100"), net.ParseIP("10.10.10.105")),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]string{"foo:10.10.10.10-10.10.10.24", "bar:10.10.9.1-10.10.9.10"},
|
||||
map[string][]*ip.Range{
|
||||
"foo": {ip.NewRange(net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24"))},
|
||||
"bar": {ip.NewRange(net.ParseIP("10.10.9.1"), net.ParseIP("10.10.9.10"))},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
iprs, err := parseContainerNetworkIPRanges(te.cps)
|
||||
if te.err != nil {
|
||||
if err == nil {
|
||||
t.Fatalf("parseContainerNetworkIPRanges(%s) => (%v, nil) want (nil, err)", te.cps, iprs)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err != te.err ||
|
||||
len(iprs) != len(te.iprs) {
|
||||
t.Fatalf("parseContainerNetworkIPRanges(%s) => (%v, %s) want (%v, %s)", te.cps, iprs, err, te.iprs, te.err)
|
||||
}
|
||||
|
||||
for v, ipr := range iprs {
|
||||
if ipr2, ok := te.iprs[v]; !ok {
|
||||
t.Fatalf("parseContainerNetworkIPRanges(%s) => (%v, %s) want (%v, %s)", te.cps, iprs, err, te.iprs, te.err)
|
||||
} else {
|
||||
for _, i := range ipr {
|
||||
found := false
|
||||
for _, i2 := range ipr2 {
|
||||
if i.Equal(i2) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatalf("parseContainerNetworkIPRanges(%s) => (%v, %s) want (%v, %s)", te.cps, iprs, err, te.iprs, te.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseContainerNetworkDNS(t *testing.T) {
|
||||
var tests = []struct {
|
||||
cds []string
|
||||
dns map[string][]net.IP
|
||||
err error
|
||||
}{
|
||||
{[]string{""}, nil, fmt.Errorf("")},
|
||||
{[]string{"foo:"}, nil, fmt.Errorf("")},
|
||||
{[]string{":"}, nil, fmt.Errorf("")},
|
||||
{[]string{":10.10.10.10"}, nil, fmt.Errorf("")},
|
||||
{[]string{":foo"}, nil, fmt.Errorf("")},
|
||||
{[]string{"foo:10"}, nil, fmt.Errorf("")},
|
||||
{[]string{"foo:10.10.10.109"}, map[string][]net.IP{"foo": {net.ParseIP("10.10.10.109")}}, nil},
|
||||
{[]string{"foo:10.10.10.109", "foo:10.10.10.110", "bar:10.10.9.109", "bar:10.10.9.110"},
|
||||
map[string][]net.IP{
|
||||
"foo": {net.ParseIP("10.10.10.109"), net.ParseIP("10.10.10.110")},
|
||||
"bar": {net.ParseIP("10.10.9.109"), net.ParseIP("10.10.9.110")},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
dns, err := parseContainerNetworkDNS(te.cds)
|
||||
if te.err != nil {
|
||||
if err == nil {
|
||||
t.Fatalf("parseContainerNetworkDNS(%s) => (%v, nil) want (nil, err)", te.cds, dns)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err != te.err ||
|
||||
len(dns) != len(te.dns) {
|
||||
t.Fatalf("parseContainerNetworkDNS(%s) => (%v, %s) want (%v, %s)", te.cds, dns, err, te.dns, te.err)
|
||||
}
|
||||
|
||||
for v, d := range dns {
|
||||
if d2, ok := te.dns[v]; !ok {
|
||||
t.Fatalf("parseContainerNetworkDNS(%s) => (%v, %s) want (%v, %s)", te.cds, dns, err, te.dns, te.err)
|
||||
} else {
|
||||
for _, i := range d {
|
||||
found := false
|
||||
for _, i2 := range d2 {
|
||||
if i.Equal(i2) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatalf("parseContainerNetworkDNS(%s) => (%v, %s) want (%v, %s)", te.cds, dns, err, te.dns, te.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
vendor/github.com/vmware/vic/cmd/vic-machine/common/compute.go
generated
vendored
Normal file
46
vendor/github.com/vmware/vic/cmd/vic-machine/common/compute.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import "gopkg.in/urfave/cli.v1"
|
||||
|
||||
type Compute struct {
|
||||
ComputeResourcePath string `cmd:"compute-resource"`
|
||||
DisplayName string `cmd:"name"`
|
||||
}
|
||||
|
||||
func (c *Compute) ComputeFlags() []cli.Flag {
|
||||
nameFlag := []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Value: "virtual-container-host",
|
||||
Usage: "The name of the Virtual Container Host",
|
||||
Destination: &c.DisplayName,
|
||||
},
|
||||
}
|
||||
|
||||
return append(nameFlag, c.ComputeFlagsNoName()...)
|
||||
}
|
||||
|
||||
func (c *Compute) ComputeFlagsNoName() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "compute-resource, r",
|
||||
Value: "",
|
||||
Usage: "Compute resource path, e.g. myCluster",
|
||||
Destination: &c.ComputeResourcePath,
|
||||
},
|
||||
}
|
||||
}
|
||||
41
vendor/github.com/vmware/vic/cmd/vic-machine/common/container.go
generated
vendored
Normal file
41
vendor/github.com/vmware/vic/cmd/vic-machine/common/container.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
type ContainerConfig struct {
|
||||
// NameConvention
|
||||
ContainerNameConvention string `cmd:"container-name-convention"`
|
||||
}
|
||||
|
||||
func (c *ContainerConfig) ContainerFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
// container naming convention
|
||||
cli.StringFlag{
|
||||
Name: "container-name-convention, cnc",
|
||||
Value: "",
|
||||
Usage: "Provide a naming convention. Allows a token of '{name}' or '{id}', that will be replaced.",
|
||||
Destination: &c.ContainerNameConvention,
|
||||
Hidden: true,
|
||||
},
|
||||
// other container flags to to added"
|
||||
// default container memory
|
||||
// default container cpu
|
||||
// default container network
|
||||
}
|
||||
}
|
||||
36
vendor/github.com/vmware/vic/cmd/vic-machine/common/debug.go
generated
vendored
Normal file
36
vendor/github.com/vmware/vic/cmd/vic-machine/common/debug.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/pkg/flags"
|
||||
)
|
||||
|
||||
type Debug struct {
|
||||
Debug *int `cmd:"debug"`
|
||||
}
|
||||
|
||||
func (d *Debug) DebugFlags(hidden bool) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.GenericFlag{
|
||||
Name: "debug, v",
|
||||
Value: flags.NewOptionalInt(&d.Debug),
|
||||
Usage: "[0(default),1...n], 0 is disabled, 1 is enabled, >= 1 may alter behaviour",
|
||||
Hidden: hidden,
|
||||
},
|
||||
}
|
||||
}
|
||||
66
vendor/github.com/vmware/vic/cmd/vic-machine/common/dns.go
generated
vendored
Normal file
66
vendor/github.com/vmware/vic/cmd/vic-machine/common/dns.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// general dns
|
||||
type DNS struct {
|
||||
DNS cli.StringSlice `arg:"dns-server"`
|
||||
IsSet bool
|
||||
}
|
||||
|
||||
func (d *DNS) DNSFlags(hidden bool) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "dns-server",
|
||||
Value: &d.DNS,
|
||||
Usage: "DNS server for the client, public, and management networks. Defaults to 8.8.8.8 and 8.8.4.4 when VCH uses static IP",
|
||||
Hidden: hidden,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// processDNSServers parses DNS servers used for client, public, mgmt networks
|
||||
func (d *DNS) ProcessDNSServers(op trace.Operation) ([]net.IP, error) {
|
||||
var parsedDNS []net.IP
|
||||
if len(d.DNS) > 0 {
|
||||
d.IsSet = true
|
||||
}
|
||||
|
||||
for _, n := range d.DNS {
|
||||
if n != "" {
|
||||
s := net.ParseIP(n)
|
||||
if s == nil {
|
||||
return nil, errors.Errorf("Invalid DNS server specified: %s", n)
|
||||
}
|
||||
parsedDNS = append(parsedDNS, s)
|
||||
}
|
||||
}
|
||||
|
||||
if len(parsedDNS) > 3 {
|
||||
op.Warn("Maximum of 3 DNS servers allowed. Additional servers specified will be ignored.")
|
||||
}
|
||||
|
||||
op.Debugf("VCH DNS servers: %s", parsedDNS)
|
||||
return parsedDNS, nil
|
||||
}
|
||||
35
vendor/github.com/vmware/vic/cmd/vic-machine/common/id.go
generated
vendored
Normal file
35
vendor/github.com/vmware/vic/cmd/vic-machine/common/id.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
type VCHID struct {
|
||||
// VCH id
|
||||
ID string
|
||||
}
|
||||
|
||||
func (i *VCHID) IDFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "id",
|
||||
Value: "",
|
||||
Usage: "The ID of the Virtual Container Host, e.g. vm-220",
|
||||
Destination: &i.ID,
|
||||
},
|
||||
}
|
||||
}
|
||||
196
vendor/github.com/vmware/vic/cmd/vic-machine/common/images.go
generated
vendored
Normal file
196
vendor/github.com/vmware/vic/cmd/vic-machine/common/images.go
generated
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
)
|
||||
|
||||
const (
|
||||
ApplianceImageKey = "core"
|
||||
LinuxImageKey = "linux"
|
||||
ApplianceImageName = "appliance.iso"
|
||||
LinuxImageName = "bootstrap.iso"
|
||||
|
||||
// An ISO 9660 sector is normally 2 KiB long. Although the specification allows for alternative sector sizes, you will rarely find anything other than 2 KiB.
|
||||
ISO9660SectorSize = 2048
|
||||
ISOVolumeSector = 0x10
|
||||
PublisherOffset = 318
|
||||
)
|
||||
|
||||
var (
|
||||
images = map[string][]string{
|
||||
ApplianceImageKey: {ApplianceImageName},
|
||||
LinuxImageKey: {LinuxImageName},
|
||||
}
|
||||
)
|
||||
|
||||
type Images struct {
|
||||
ApplianceISO string
|
||||
BootstrapISO string
|
||||
OSType string
|
||||
}
|
||||
|
||||
func (i *Images) ImageFlags(hidden bool) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "appliance-iso, ai",
|
||||
Value: "",
|
||||
Usage: "The appliance iso",
|
||||
Destination: &i.ApplianceISO,
|
||||
Hidden: hidden,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "bootstrap-iso, bi",
|
||||
Value: "",
|
||||
Usage: "The bootstrap iso",
|
||||
Destination: &i.BootstrapISO,
|
||||
Hidden: hidden,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Images) CheckImagesFiles(op trace.Operation, force bool) (map[string]string, error) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
i.OSType = "linux"
|
||||
// detect images files
|
||||
osImgs, ok := images[i.OSType]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Specified OS %q is not known to this installer", i.OSType)
|
||||
}
|
||||
|
||||
imgs := make(map[string]string)
|
||||
result := make(map[string]string)
|
||||
if i.ApplianceISO == "" {
|
||||
i.ApplianceISO = images[ApplianceImageKey][0]
|
||||
}
|
||||
imgs[ApplianceImageName] = i.ApplianceISO
|
||||
|
||||
if i.BootstrapISO == "" {
|
||||
i.BootstrapISO = osImgs[0]
|
||||
}
|
||||
imgs[LinuxImageName] = i.BootstrapISO
|
||||
|
||||
for name, img := range imgs {
|
||||
_, err := os.Stat(img)
|
||||
if os.IsNotExist(err) {
|
||||
var dir string
|
||||
dir, err = filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
_, err = os.Stat(filepath.Join(dir, img))
|
||||
if err == nil {
|
||||
img = filepath.Join(dir, img)
|
||||
}
|
||||
}
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
op.Warnf("\t\tUnable to locate %s in the current or installer directory.", img)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
version, err := i.checkImageVersion(op, img, force)
|
||||
if err != nil {
|
||||
op.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
versionedName := fmt.Sprintf("%s-%s", version, name)
|
||||
result[versionedName] = img
|
||||
if name == ApplianceImageName {
|
||||
i.ApplianceISO = versionedName
|
||||
} else {
|
||||
i.BootstrapISO = versionedName
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetImageVersion will read iso file version from Primary Volume Descriptor, field "Publisher Identifier"
|
||||
func (i *Images) GetImageVersion(op trace.Operation, img string) (string, error) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
f, err := os.Open(img)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("failed to open iso file %q: %s", img, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// System area goes from sectors 0x00 to 0x0F. Volume descriptors can be
|
||||
// found starting at sector 0x10
|
||||
|
||||
_, err = f.Seek(int64(ISOVolumeSector*ISO9660SectorSize)+PublisherOffset, 0)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("failed to locate iso version section in file %q: %s", img, err)
|
||||
}
|
||||
publisherBytes := make([]byte, 128)
|
||||
size, err := f.Read(publisherBytes)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("failed to read iso version in file %q: %s", img, err)
|
||||
}
|
||||
if size == 0 {
|
||||
return "", errors.Errorf("version is not set in iso file %q", img)
|
||||
}
|
||||
|
||||
versions := strings.Fields(string(publisherBytes[:size]))
|
||||
if len(versions) > 0 {
|
||||
return versions[len(versions)-1], nil
|
||||
}
|
||||
return "version-unknown", nil
|
||||
}
|
||||
|
||||
func (i *Images) checkImageVersion(op trace.Operation, img string, force bool) (string, error) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
ver, err := i.GetImageVersion(op, img)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sv := i.getNoCommitHashVersion(op, ver)
|
||||
if sv == "" {
|
||||
op.Debugf("Version is not set in %q", img)
|
||||
ver = ""
|
||||
}
|
||||
|
||||
installerSV := i.getNoCommitHashVersion(op, version.GetBuild().ShortVersion())
|
||||
|
||||
// here compare version without last commit hash, to make developer life easier
|
||||
if !strings.EqualFold(installerSV, sv) {
|
||||
message := fmt.Sprintf("iso file %q version %q inconsistent with installer version %q", img, strings.ToLower(ver), version.GetBuild().ShortVersion())
|
||||
if !force {
|
||||
return "", errors.Errorf("%s. Specify --force to force create. ", message)
|
||||
}
|
||||
op.Warn(message)
|
||||
}
|
||||
return ver, nil
|
||||
}
|
||||
|
||||
func (i *Images) getNoCommitHashVersion(op trace.Operation, version string) string {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
j := strings.LastIndex(version, "-")
|
||||
if j == -1 {
|
||||
return ""
|
||||
}
|
||||
return version[:j]
|
||||
}
|
||||
121
vendor/github.com/vmware/vic/cmd/vic-machine/common/images_test.go
generated
vendored
Normal file
121
vendor/github.com/vmware/vic/cmd/vic-machine/common/images_test.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
image = &Images{}
|
||||
)
|
||||
|
||||
func TestImageNotFound(t *testing.T) {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
tmpfile, err := ioutil.TempFile("", "appIso")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer os.Remove(tmpfile.Name()) // clean up
|
||||
|
||||
op := trace.NewOperation(context.Background(), "TestImageNotFound")
|
||||
|
||||
image.ApplianceISO = tmpfile.Name()
|
||||
image.OSType = "linux"
|
||||
if _, err = image.CheckImagesFiles(op, false); err == nil {
|
||||
t.Errorf("Error is expected for boot iso file is not found.")
|
||||
}
|
||||
}
|
||||
|
||||
func writeImageVersion(fileName string, version string) error {
|
||||
f, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := f.Truncate(int64(0x10*2048) + 318); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.WriteAt([]byte(version), int64(0x10*2048)+318); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestImageChecks(t *testing.T) {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
tmpfile, err := ioutil.TempFile("", "bootIso")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer os.Remove(tmpfile.Name()) // clean up
|
||||
|
||||
op := trace.NewOperation(context.Background(), "TestImageChecks")
|
||||
|
||||
_, err = os.Create("appliance.iso")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create default appliance iso file")
|
||||
}
|
||||
defer os.Remove("appliance.iso")
|
||||
|
||||
image.ApplianceISO = ""
|
||||
image.BootstrapISO = tmpfile.Name()
|
||||
image.OSType = "linux"
|
||||
var imageFiles map[string]string
|
||||
if _, err = image.CheckImagesFiles(op, false); err == nil {
|
||||
t.Errorf("Error is expected")
|
||||
}
|
||||
|
||||
if err = writeImageVersion("appliance.iso", "Inc. 0.1-000-abcd"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err = writeImageVersion(tmpfile.Name(), "Inc. 0.1-000-abcd"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cliContext := &cli.Context{
|
||||
App: &cli.App{
|
||||
Version: "Inconsistent",
|
||||
},
|
||||
}
|
||||
if _, err = image.CheckImagesFiles(op, false); err == nil {
|
||||
t.Errorf("Error is expected")
|
||||
}
|
||||
|
||||
cliContext.App.Version = "0.1-000-abcd"
|
||||
if imageFiles, err = image.CheckImagesFiles(op, true); err != nil {
|
||||
t.Errorf("Error is returned: %s", err)
|
||||
}
|
||||
found := false
|
||||
for _, file := range imageFiles {
|
||||
if file == tmpfile.Name() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Image file list does not contain input, %s", imageFiles)
|
||||
}
|
||||
}
|
||||
59
vendor/github.com/vmware/vic/cmd/vic-machine/common/kubelet.go
generated
vendored
Normal file
59
vendor/github.com/vmware/vic/cmd/vic-machine/common/kubelet.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/flags"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// Kubelet holds credentials for the VCH operations user
|
||||
type Kubelet struct {
|
||||
ServerAddress *string `cmd:"kubelet"`
|
||||
ConfigFile *string
|
||||
}
|
||||
|
||||
func (v *Kubelet) Flags(hidden bool) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.GenericFlag{
|
||||
Name: "k8s-server-address",
|
||||
Value: flags.NewOptionalString(&v.ServerAddress),
|
||||
Usage: "The Kubernetes Server URL, <hostname/ip>:<port>",
|
||||
Hidden: hidden,
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: "k8s-config",
|
||||
Value: flags.NewOptionalString(&v.ConfigFile),
|
||||
Usage: "Kubernetes client config file",
|
||||
Hidden: hidden,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Kubelet) ProcessKubelet(op trace.Operation, isCreateOp bool) error {
|
||||
if isCreateOp {
|
||||
if v.ServerAddress != nil && v.ConfigFile == nil {
|
||||
return errors.Errorf("A Kubernetes Config File must be specified when specifying a Kubernetes Server Address")
|
||||
}
|
||||
if v.ServerAddress == nil && v.ConfigFile != nil {
|
||||
return errors.Errorf("A Kubernetes Server Address must be specified when specifying a Kubernetes Config File")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
15
vendor/github.com/vmware/vic/cmd/vic-machine/common/kubelet_test.go
generated
vendored
Normal file
15
vendor/github.com/vmware/vic/cmd/vic-machine/common/kubelet_test.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
78
vendor/github.com/vmware/vic/cmd/vic-machine/common/limits.go
generated
vendored
Normal file
78
vendor/github.com/vmware/vic/cmd/vic-machine/common/limits.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/flags"
|
||||
)
|
||||
|
||||
type ResourceLimits struct {
|
||||
VCHCPULimitsMHz *int `cmd:"cpu"`
|
||||
VCHCPUReservationsMHz *int `cmd:"cpu-reservation"`
|
||||
VCHCPUShares *types.SharesInfo `cmd:"cpu-shares"`
|
||||
|
||||
VCHMemoryLimitsMB *int `cmd:"memory"`
|
||||
VCHMemoryReservationsMB *int `cmd:"memory-reservation"`
|
||||
VCHMemoryShares *types.SharesInfo `cmd:"memory-shares"`
|
||||
|
||||
IsSet bool
|
||||
}
|
||||
|
||||
func (r *ResourceLimits) VCHMemoryLimitFlags(hidden bool) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.GenericFlag{
|
||||
Name: "memory, mem",
|
||||
Value: flags.NewOptionalInt(&r.VCHMemoryLimitsMB),
|
||||
Usage: "VCH resource pool memory limit in MB (unlimited=0)",
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: "memory-reservation, memr",
|
||||
Value: flags.NewOptionalInt(&r.VCHMemoryReservationsMB),
|
||||
Usage: "VCH resource pool memory reservation in MB",
|
||||
Hidden: hidden,
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: "memory-shares, mems",
|
||||
Value: flags.NewSharesFlag(&r.VCHMemoryShares),
|
||||
Usage: "VCH resource pool memory shares in level or share number, e.g. high, normal, low, or 163840",
|
||||
Hidden: hidden,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ResourceLimits) VCHCPULimitFlags(hidden bool) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.GenericFlag{
|
||||
Name: "cpu",
|
||||
Value: flags.NewOptionalInt(&r.VCHCPULimitsMHz),
|
||||
Usage: "VCH resource pool vCPUs limit in MHz (unlimited=0)",
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: "cpu-reservation, cpur",
|
||||
Value: flags.NewOptionalInt(&r.VCHCPUReservationsMHz),
|
||||
Usage: "VCH resource pool reservation in MHz",
|
||||
Hidden: hidden,
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: "cpu-shares, cpus",
|
||||
Value: flags.NewSharesFlag(&r.VCHCPUShares),
|
||||
Usage: "VCH VCH resource pool vCPUs shares, in level or share number, e.g. high, normal, low, or 4000",
|
||||
Hidden: hidden,
|
||||
},
|
||||
}
|
||||
}
|
||||
29
vendor/github.com/vmware/vic/cmd/vic-machine/common/networks.go
generated
vendored
Normal file
29
vendor/github.com/vmware/vic/cmd/vic-machine/common/networks.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
type Networks struct {
|
||||
ClientNetworkName string
|
||||
ClientNetworkIP string
|
||||
ClientNetworkGateway string
|
||||
|
||||
PublicNetworkName string
|
||||
PublicNetworkIP string
|
||||
PublicNetworkGateway string
|
||||
|
||||
ManagementNetworkName string
|
||||
ManagementNetworkIP string
|
||||
ManagementNetworkGateway string
|
||||
}
|
||||
38
vendor/github.com/vmware/vic/cmd/vic-machine/common/operation.go
generated
vendored
Normal file
38
vendor/github.com/vmware/vic/cmd/vic-machine/common/operation.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
func NewOperation(clic *cli.Context, debug *int) trace.Operation {
|
||||
op := trace.NewOperation(context.Background(), clic.App.Name)
|
||||
op.Logger = logrus.New()
|
||||
op.Logger.Out = clic.App.Writer
|
||||
|
||||
if debug != nil && *debug > 0 {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
trace.Logger.Level = logrus.DebugLevel
|
||||
op.Logger.Level = logrus.DebugLevel
|
||||
}
|
||||
|
||||
return op
|
||||
}
|
||||
112
vendor/github.com/vmware/vic/cmd/vic-machine/common/ops_credentials.go
generated
vendored
Normal file
112
vendor/github.com/vmware/vic/cmd/vic-machine/common/ops_credentials.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/flags"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// OpsCredentials holds credentials for the VCH operations user
|
||||
type OpsCredentials struct {
|
||||
OpsUser *string `cmd:"ops-user"`
|
||||
OpsPassword *string
|
||||
GrantPerms *bool
|
||||
IsSet bool
|
||||
}
|
||||
|
||||
func (o *OpsCredentials) Flags(hidden bool) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.GenericFlag{
|
||||
Name: "ops-user",
|
||||
Value: flags.NewOptionalString(&o.OpsUser),
|
||||
Usage: "The user with which the VCH operates after creation. Defaults to the credential supplied with target",
|
||||
Hidden: hidden,
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: "ops-password",
|
||||
Value: flags.NewOptionalString(&o.OpsPassword),
|
||||
Usage: "Password or token for the operations user. Defaults to the credential supplied with target",
|
||||
Hidden: hidden,
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: "ops-grant-perms",
|
||||
Value: flags.NewOptionalBool(&o.GrantPerms),
|
||||
Usage: "Create roles and grant required permissions to the specified ops-use",
|
||||
Hidden: hidden,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessOpsCredentials processes fields for the VCH operations user. When invoked
|
||||
// during a VCH create operation, adminUser and adminPassword must be supplied to
|
||||
// be used as ops credentials if they are not specified by the user. For a configure
|
||||
// operation, adminUser and adminPassword are not needed.
|
||||
func (o *OpsCredentials) ProcessOpsCredentials(op trace.Operation, isCreateOp bool, adminUser string, adminPassword *string) error {
|
||||
if o.OpsUser == nil && o.OpsPassword != nil {
|
||||
return errors.New("Password for operations user specified without user having been specified")
|
||||
}
|
||||
|
||||
if isCreateOp {
|
||||
if o.OpsUser == nil {
|
||||
// Check if there was a request to setup ops-user Roles and Permissions
|
||||
if o.GrantPerms != nil {
|
||||
// If true return error
|
||||
if *o.GrantPerms {
|
||||
return errors.Errorf("Invalid use of flag: --ops-grant-perms. Cannot setup Roles and Permissions for administrative user.")
|
||||
}
|
||||
// If false ignore
|
||||
o.GrantPerms = nil
|
||||
}
|
||||
op.Warn("Using administrative user for VCH operation - use --ops-user to improve security (see -x for advanced help)")
|
||||
o.OpsUser = &adminUser
|
||||
if adminPassword == nil {
|
||||
return errors.New("Unable to use nil password from administrative user for operations user")
|
||||
}
|
||||
o.OpsPassword = adminPassword
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if o.OpsUser != nil {
|
||||
o.IsSet = true
|
||||
}
|
||||
}
|
||||
|
||||
if o.OpsPassword != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prompt for the ops password only during a create operation or a configure
|
||||
// operation where the ops user is specified.
|
||||
if isCreateOp || o.IsSet {
|
||||
op.Infof("vSphere password for %s: ", *o.OpsUser)
|
||||
b, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
message := fmt.Sprintf("Failed to read password from stdin: %s", err)
|
||||
cli.NewExitError(message, 1)
|
||||
}
|
||||
sb := string(b)
|
||||
o.OpsPassword = &sb
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
109
vendor/github.com/vmware/vic/cmd/vic-machine/common/ops_credentials_test.go
generated
vendored
Normal file
109
vendor/github.com/vmware/vic/cmd/vic-machine/common/ops_credentials_test.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
func TestProcessOpsCredentials(t *testing.T) {
|
||||
createOps := &OpsCredentials{}
|
||||
isCreateOp := true
|
||||
adminUser := "admin"
|
||||
adminPassword := ""
|
||||
|
||||
op := trace.NewOperation(context.Background(), "TestProcessOpsCredentials")
|
||||
|
||||
// There should be an error if the admin password is not specified for a create operation.
|
||||
err := createOps.ProcessOpsCredentials(op, isCreateOp, adminUser, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
err = createOps.ProcessOpsCredentials(op, isCreateOp, adminUser, &adminPassword)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, *createOps.OpsUser, adminUser)
|
||||
assert.Equal(t, *createOps.OpsPassword, adminPassword)
|
||||
|
||||
opsUser := "op"
|
||||
opsPassword := "opPass"
|
||||
createOps.OpsUser = &opsUser
|
||||
createOps.OpsPassword = &opsPassword
|
||||
err = createOps.ProcessOpsCredentials(op, isCreateOp, adminUser, &adminPassword)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, *createOps.OpsUser, opsUser)
|
||||
assert.Equal(t, *createOps.OpsPassword, opsPassword)
|
||||
|
||||
// Ensure that fields are set correctly for a configure operation.
|
||||
configureOps := &OpsCredentials{
|
||||
OpsUser: &opsUser,
|
||||
OpsPassword: &opsPassword,
|
||||
}
|
||||
isCreateOp = false
|
||||
err = configureOps.ProcessOpsCredentials(op, isCreateOp, "", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, configureOps.IsSet)
|
||||
assert.Equal(t, *createOps.OpsUser, opsUser)
|
||||
assert.Equal(t, *createOps.OpsPassword, opsPassword)
|
||||
|
||||
// There should be an error if the ops-password is specified without ops-user.
|
||||
configureOps.OpsUser = nil
|
||||
err = configureOps.ProcessOpsCredentials(op, isCreateOp, "", nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Test correct grant permissions
|
||||
createOps = &OpsCredentials{}
|
||||
createOps.OpsUser = &opsUser
|
||||
createOps.OpsPassword = &opsPassword
|
||||
grantPerms := true
|
||||
createOps.GrantPerms = &grantPerms
|
||||
err = createOps.ProcessOpsCredentials(op, true, adminUser, &adminPassword)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, *createOps.OpsUser, opsUser)
|
||||
assert.Equal(t, *createOps.OpsPassword, opsPassword)
|
||||
assert.True(t, *createOps.GrantPerms)
|
||||
|
||||
// Create Negative test: grantPerms is set to true but there is no ops-user,
|
||||
// grantPerms should be reset to false
|
||||
createOps = &OpsCredentials{}
|
||||
createOps.OpsUser = nil
|
||||
createOps.OpsPassword = nil
|
||||
grantPerms = true
|
||||
createOps.GrantPerms = &grantPerms
|
||||
err = createOps.ProcessOpsCredentials(op, true, adminUser, &adminPassword)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Create Negative test: grantPerms is set to true but there is no ops-user,
|
||||
// grantPerms should be reset to false
|
||||
createOps = &OpsCredentials{}
|
||||
createOps.OpsUser = nil
|
||||
createOps.OpsPassword = nil
|
||||
grantPerms = false
|
||||
createOps.GrantPerms = &grantPerms
|
||||
err = createOps.ProcessOpsCredentials(op, true, adminUser, &adminPassword)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, createOps.GrantPerms)
|
||||
|
||||
// Configure test: grantPerms is set to true but there is no ops-user,
|
||||
// grantPerms should be true as the ops-user may come from the config
|
||||
grantPerms = true
|
||||
createOps.GrantPerms = &grantPerms
|
||||
err = createOps.ProcessOpsCredentials(op, false, adminUser, &adminPassword)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, *createOps.GrantPerms)
|
||||
}
|
||||
70
vendor/github.com/vmware/vic/cmd/vic-machine/common/proxy.go
generated
vendored
Normal file
70
vendor/github.com/vmware/vic/cmd/vic-machine/common/proxy.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/vmware/vic/pkg/flags"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
type Proxies struct {
|
||||
HTTPSProxy *string
|
||||
HTTPProxy *string
|
||||
IsSet bool
|
||||
}
|
||||
|
||||
func (p *Proxies) ProxyFlags(hidden bool) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
// proxies
|
||||
cli.GenericFlag{
|
||||
Name: "https-proxy",
|
||||
Value: flags.NewOptionalString(&p.HTTPSProxy),
|
||||
Usage: "An HTTPS proxy for use when fetching images, in the form https://fqdn_or_ip:port",
|
||||
Hidden: hidden,
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: "http-proxy",
|
||||
Value: flags.NewOptionalString(&p.HTTPProxy),
|
||||
Usage: "An HTTP proxy for use when fetching images, in the form http://fqdn_or_ip:port",
|
||||
Hidden: hidden,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Proxies) ProcessProxies() (hproxy, sproxy *url.URL, err error) {
|
||||
if p.HTTPProxy != nil || p.HTTPSProxy != nil {
|
||||
p.IsSet = true
|
||||
}
|
||||
if p.HTTPProxy != nil && *p.HTTPProxy != "" {
|
||||
hproxy, err = url.Parse(*p.HTTPProxy)
|
||||
if err != nil || hproxy.Host == "" || hproxy.Scheme != "http" {
|
||||
err = cli.NewExitError(fmt.Sprintf("Could not parse HTTP proxy - expected format http://fqnd_or_ip:port: %s", *p.HTTPProxy), 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if p.HTTPSProxy != nil && *p.HTTPSProxy != "" {
|
||||
sproxy, err = url.Parse(*p.HTTPSProxy)
|
||||
if err != nil || sproxy.Host == "" || sproxy.Scheme != "https" {
|
||||
err = cli.NewExitError(fmt.Sprintf("Could not parse HTTPS proxy - expected format https://fqnd_or_ip:port: %s", *p.HTTPSProxy), 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
82
vendor/github.com/vmware/vic/cmd/vic-machine/common/registries.go
generated
vendored
Normal file
82
vendor/github.com/vmware/vic/cmd/vic-machine/common/registries.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// Registries contains metadata used to create/configure registry CA data
|
||||
type Registries struct {
|
||||
RegistryCAsArg cli.StringSlice `arg:"registry-ca"`
|
||||
InsecureRegistriesArg cli.StringSlice `arg:"insecure-registry"`
|
||||
WhitelistRegistriesArg cli.StringSlice `arg:"whitelist-registry"`
|
||||
|
||||
RegistryCAs []byte
|
||||
|
||||
InsecureRegistries []string `cmd:"insecure-registry"`
|
||||
WhitelistRegistries []string `cmd:"whitelist-registry"`
|
||||
}
|
||||
|
||||
// Flags generates command line flags
|
||||
func (r *Registries) Flags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "registry-ca, rc",
|
||||
Usage: "Specify a list of additional certificate authority files to use to verify secure registry servers",
|
||||
Value: &r.RegistryCAsArg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// LoadRegistryCAs loads additional CA certs for docker registry usage
|
||||
func (r *Registries) loadRegistryCAs(op trace.Operation) ([]byte, error) {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
var registryCerts []byte
|
||||
for _, f := range r.RegistryCAsArg {
|
||||
b, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
err = errors.Errorf("Failed to load authority from file %s: %s", f, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
registryCerts = append(registryCerts, b...)
|
||||
op.Infof("Loaded registry CA from %s", f)
|
||||
}
|
||||
|
||||
return registryCerts, nil
|
||||
}
|
||||
|
||||
func (r *Registries) ProcessRegistries(op trace.Operation) error {
|
||||
// load additional certificate authorities for use with registries
|
||||
if len(r.RegistryCAsArg) > 0 {
|
||||
registryCAs, err := r.loadRegistryCAs(op)
|
||||
if err != nil {
|
||||
return errors.Errorf("Unable to load CA certificates for registry logins: %s", err)
|
||||
}
|
||||
|
||||
r.RegistryCAs = registryCAs
|
||||
}
|
||||
|
||||
r.InsecureRegistries = r.InsecureRegistriesArg.Value()
|
||||
r.WhitelistRegistries = r.WhitelistRegistriesArg.Value()
|
||||
return nil
|
||||
}
|
||||
118
vendor/github.com/vmware/vic/cmd/vic-machine/common/target.go
generated
vendored
Normal file
118
vendor/github.com/vmware/vic/cmd/vic-machine/common/target.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/pkg/flags"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
type Target struct {
|
||||
URL *url.URL `cmd:"target"`
|
||||
|
||||
User string
|
||||
Password *string
|
||||
CloneTicket string
|
||||
Thumbprint string `cmd:"thumbprint"`
|
||||
}
|
||||
|
||||
func NewTarget() *Target {
|
||||
return &Target{}
|
||||
}
|
||||
|
||||
func (t *Target) TargetFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.GenericFlag{
|
||||
Name: "target, t",
|
||||
Value: flags.NewURLFlag(&t.URL),
|
||||
Usage: "REQUIRED. ESXi or vCenter connection URL, specifying a datacenter if multiple exist e.g. root:password@VC-FQDN/datacenter",
|
||||
EnvVar: "VIC_MACHINE_TARGET",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "user, u",
|
||||
Value: "",
|
||||
Usage: "ESX or vCenter user",
|
||||
Destination: &t.User,
|
||||
EnvVar: "VIC_MACHINE_USER",
|
||||
},
|
||||
cli.GenericFlag{
|
||||
Name: "password, p",
|
||||
Value: flags.NewOptionalString(&t.Password),
|
||||
Usage: "ESX or vCenter password",
|
||||
EnvVar: "VIC_MACHINE_PASSWORD",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "thumbprint",
|
||||
Value: "",
|
||||
Destination: &t.Thumbprint,
|
||||
Usage: "ESX or vCenter host certificate thumbprint",
|
||||
EnvVar: "VIC_MACHINE_THUMBPRINT",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// HasCredentials check that the credentials have been supplied by any of the permitted mechanisms
|
||||
func (t *Target) HasCredentials(op trace.Operation) error {
|
||||
if t.URL == nil {
|
||||
return cli.NewExitError("--target argument must be specified", 1)
|
||||
}
|
||||
|
||||
// assume if a vsphere session key exists, we want to use that instead of user/pass
|
||||
if t.CloneTicket != "" {
|
||||
t.URL.User = nil // necessary?
|
||||
return nil
|
||||
}
|
||||
|
||||
var urlUser string
|
||||
var urlPassword *string
|
||||
|
||||
if t.URL.User != nil {
|
||||
urlUser = t.URL.User.Username()
|
||||
if passwd, set := t.URL.User.Password(); set {
|
||||
urlPassword = &passwd
|
||||
}
|
||||
}
|
||||
if t.User == "" && urlUser == "" {
|
||||
return cli.NewExitError("vSphere user must be specified, either with --user or as part of --target", 1)
|
||||
} else if t.User == "" && urlUser != "" {
|
||||
t.User = urlUser
|
||||
}
|
||||
|
||||
//prompt for passwd if not specified
|
||||
if t.Password == nil && urlPassword == nil {
|
||||
op.Infof("vSphere password for %s: ", t.User)
|
||||
b, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
message := fmt.Sprintf("Failed to read password from stdin: %s", err)
|
||||
cli.NewExitError(message, 1)
|
||||
}
|
||||
sb := string(b)
|
||||
t.Password = &sb
|
||||
} else if t.Password == nil && urlPassword != nil {
|
||||
t.Password = urlPassword
|
||||
}
|
||||
|
||||
// Used by vic-machine for Session login
|
||||
t.URL.User = url.UserPassword(t.User, *t.Password)
|
||||
|
||||
return nil
|
||||
}
|
||||
92
vendor/github.com/vmware/vic/cmd/vic-machine/common/target_test.go
generated
vendored
Normal file
92
vendor/github.com/vmware/vic/cmd/vic-machine/common/target_test.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
func TestFlags(t *testing.T) {
|
||||
target := NewTarget()
|
||||
flags := target.TargetFlags()
|
||||
|
||||
if len(flags) != 4 {
|
||||
t.Errorf("Wrong flag numbers")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
op := trace.NewOperation(context.Background(), "TestProcess")
|
||||
|
||||
passwd := "pass"
|
||||
url1, _ := soap.ParseURL("127.0.0.1")
|
||||
url2, _ := soap.ParseURL("root:@127.0.0.1")
|
||||
url3, _ := soap.ParseURL("line:password@127.0.0.1")
|
||||
url4, _ := soap.ParseURL("root:pass@127.0.0.1")
|
||||
result, _ := url.Parse("https://root:pass@127.0.0.1/sdk")
|
||||
passEmpty := ""
|
||||
result1, _ := url.Parse("https://root:@127.0.0.1/sdk")
|
||||
tests := []struct {
|
||||
URL *url.URL
|
||||
User string
|
||||
Password *string
|
||||
|
||||
err error
|
||||
result *url.URL
|
||||
}{
|
||||
{nil, "", nil, cli.NewExitError("--target argument must be specified", 1), nil},
|
||||
{nil, "root", nil, cli.NewExitError("--target argument must be specified", 1), nil},
|
||||
{nil, "root", &passwd, cli.NewExitError("--target argument must be specified", 1), nil},
|
||||
{url1, "root", &passwd, nil, result},
|
||||
{url4, "", nil, nil, result},
|
||||
{url3, "root", &passwd, nil, result},
|
||||
{url2, "", &passwd, nil, result},
|
||||
{url1, "root", &passEmpty, nil, result1},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
target := NewTarget()
|
||||
target.URL = test.URL
|
||||
target.User = test.User
|
||||
target.Password = test.Password
|
||||
if target.URL != nil {
|
||||
t.Logf("Before processing, url: %s", target.URL.String())
|
||||
}
|
||||
e := target.HasCredentials(op)
|
||||
if test.err != nil {
|
||||
if e == nil {
|
||||
t.Errorf("Empty error")
|
||||
}
|
||||
if e.Error() != test.err.Error() {
|
||||
t.Errorf("Unexpected error message: %s", e.Error())
|
||||
}
|
||||
} else if e != nil {
|
||||
t.Errorf("Unexpected error %s", e.Error())
|
||||
} else {
|
||||
if target.URL != test.URL {
|
||||
t.Errorf("unexpected result url: %s", target.URL.String())
|
||||
} else {
|
||||
t.Logf("result url: %s", target.URL.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
76
vendor/github.com/vmware/vic/cmd/vic-machine/common/utils.go
generated
vendored
Normal file
76
vendor/github.com/vmware/vic/cmd/vic-machine/common/utils.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// scheme string for nfs volume store targets
|
||||
NfsScheme = "nfs"
|
||||
|
||||
// scheme string for ds volume store targets
|
||||
DsScheme = "ds"
|
||||
|
||||
// scheme string for volume store targets without a scheme
|
||||
EmptyScheme = ""
|
||||
)
|
||||
|
||||
// https://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=2046088
|
||||
const unsuppCharsRegex = `%|&|\*|\$|#|@|!|\\|/|:|\?|"|<|>|;|'|\|`
|
||||
|
||||
// Same as unsuppCharsRegex but allows / and : for datastore paths
|
||||
const unsuppCharsDatastoreRegex = `%|&|\*|\$|#|@|!|\\|\?|"|<|>|;|'|\|`
|
||||
|
||||
var reUnsupp = regexp.MustCompile(unsuppCharsRegex)
|
||||
var reUnsuppDatastore = regexp.MustCompile(unsuppCharsDatastoreRegex)
|
||||
|
||||
func LogErrorIfAny(op trace.Operation, clic *cli.Context, err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
op.Errorf("--------------------")
|
||||
op.Errorf("%s %s failed: %s\n", clic.App.Name, clic.Command.Name, errors.ErrorStack(err))
|
||||
return cli.NewExitError("", 1)
|
||||
}
|
||||
|
||||
// CheckUnsupportedChars returns an error if string contains special characters
|
||||
func CheckUnsupportedChars(s string) error {
|
||||
return checkUnsupportedChars(s, reUnsupp)
|
||||
}
|
||||
|
||||
// CheckUnsupportedCharsDatastore returns an error if a datastore string contains special characters
|
||||
func CheckUnsupportedCharsDatastore(s string) error {
|
||||
return checkUnsupportedChars(s, reUnsuppDatastore)
|
||||
}
|
||||
|
||||
func checkUnsupportedChars(s string, re *regexp.Regexp) error {
|
||||
st := []byte(s)
|
||||
var v []int
|
||||
// this is validation step for characters in a datastore URI
|
||||
if v = re.FindIndex(st); v == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unsupported character %q in %q", s[v[0]:v[1]], s)
|
||||
}
|
||||
121
vendor/github.com/vmware/vic/cmd/vic-machine/common/utils_test.go
generated
vendored
Normal file
121
vendor/github.com/vmware/vic/cmd/vic-machine/common/utils_test.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckUnsupportedchars(t *testing.T) {
|
||||
tests := []struct {
|
||||
S string
|
||||
valid bool
|
||||
}{
|
||||
{"anjunabeats", true},
|
||||
{"tony-1", true},
|
||||
{"paavo_1", true},
|
||||
{"jono(1)", true},
|
||||
{"oceanlab (1)", true},
|
||||
{"test%", false},
|
||||
{"test&", false},
|
||||
{"test*", false},
|
||||
{"test$", false},
|
||||
{"test#", false},
|
||||
{"test@", false},
|
||||
{"test!", false},
|
||||
{`test\`, false},
|
||||
{"test/", false}, // U+002F
|
||||
{"test\u002f", false}, // U+002F
|
||||
{`testЯ`, true}, // U+042F
|
||||
{"test\u042f", true}, // U+042F
|
||||
{`testį`, true}, // U+012F
|
||||
{"test\u012f", true}, // U+012F
|
||||
{"test:", false},
|
||||
{"test?", false},
|
||||
{`test"`, false},
|
||||
{"test<", false},
|
||||
{"test>", false},
|
||||
{"test;", false},
|
||||
{"test'", false},
|
||||
{"test|", false},
|
||||
{"test|", false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
err := CheckUnsupportedChars(test.S)
|
||||
if err != nil {
|
||||
if test.valid {
|
||||
t.Errorf("got %q, expected pass for %q", err, test.S)
|
||||
}
|
||||
t.Logf("test case %q passed", test.S)
|
||||
continue
|
||||
}
|
||||
if test.valid {
|
||||
t.Logf("test case %q passed", test.S)
|
||||
continue
|
||||
}
|
||||
t.Errorf("got %q, expected error for %q", err, test.S)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckUnsupportedCharsDatastore(t *testing.T) {
|
||||
tests := []struct {
|
||||
S string
|
||||
valid bool
|
||||
}{
|
||||
{"anjunabeats", true},
|
||||
{"tony-1", true},
|
||||
{"paavo_1", true},
|
||||
{"jono(1)", true},
|
||||
{"oceanlab (1)", true},
|
||||
{"waawn/", true},
|
||||
{"tristate:", true},
|
||||
{"test%", false},
|
||||
{"test&", false},
|
||||
{"test*", false},
|
||||
{"test$", false},
|
||||
{"test#", false},
|
||||
{"test@", false},
|
||||
{"test!", false}, // U+0021
|
||||
{"test\u0021", false}, // U+0021
|
||||
{`testġ`, true}, // U+0121
|
||||
{"test\u0121", true}, // U+0121
|
||||
{`test\`, false},
|
||||
{"test?", false},
|
||||
{`test"`, false},
|
||||
{"test<", false},
|
||||
{"test>", false},
|
||||
{"test;", false},
|
||||
{"test'", false},
|
||||
{"test|", false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
err := CheckUnsupportedCharsDatastore(test.S)
|
||||
|
||||
if err != nil {
|
||||
if test.valid {
|
||||
t.Errorf("got %q, expected pass for %q", err, test.S)
|
||||
}
|
||||
t.Logf("test case %q passed", test.S)
|
||||
continue
|
||||
}
|
||||
if test.valid {
|
||||
t.Logf("test case %q passed", test.S)
|
||||
continue
|
||||
}
|
||||
t.Errorf("got %q, expected error for %q", err, test.S)
|
||||
}
|
||||
}
|
||||
111
vendor/github.com/vmware/vic/cmd/vic-machine/common/volume_stores.go
generated
vendored
Normal file
111
vendor/github.com/vmware/vic/cmd/vic-machine/common/volume_stores.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
const (
|
||||
dsInputFormat = "<datastore url w/ path>:label"
|
||||
nfsInputFormat = "nfs://<host>/<url-path>?<mount option as query parameters>:<label>"
|
||||
)
|
||||
|
||||
type VolumeStores struct {
|
||||
VolumeStores cli.StringSlice `arg:"volume-store"`
|
||||
}
|
||||
|
||||
func (v *VolumeStores) Flags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "volume-store, vs",
|
||||
Value: &v.VolumeStores,
|
||||
Usage: "Specify a list of location and label for volume store, nfs stores can have mount options specified as query parameters in the url target. \n\t Examples for a vsphere backed volume store are: \"datastore/path:label\" or \"datastore:label\" or \"ds://my-datastore-name:store-label\"\n\t Examples for nfs back volume stores are: \"nfs://127.0.0.1/path/to/share/point?uid=1234&gid=5678&proto=tcp:my-volume-store-label\" or \"nfs://my-store/path/to/share/point:my-label\"",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VolumeStores) ProcessVolumeStores() (map[string]*url.URL, error) {
|
||||
volumeLocations := make(map[string]*url.URL)
|
||||
|
||||
for _, arg := range v.VolumeStores {
|
||||
urlTarget, rawTarget, label, err := processVolumeStoreParam(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch urlTarget.Scheme {
|
||||
case NfsScheme:
|
||||
// nothing needs to be done here. parsing the url is enough for pre-validation checking of an nfs target.
|
||||
case EmptyScheme, DsScheme:
|
||||
// a datastore target is our default assumption
|
||||
urlTarget.Scheme = DsScheme
|
||||
if err := CheckUnsupportedCharsDatastore(rawTarget); err != nil {
|
||||
return nil, fmt.Errorf("--volume-store contains unsupported characters for datastore target: %s Allowed characters are alphanumeric, space and symbols - _ ( ) / : ,", err)
|
||||
}
|
||||
|
||||
if len(urlTarget.RawQuery) > 0 {
|
||||
return nil, fmt.Errorf("volume store input must be in format datastore/path:label or %s", nfsInputFormat)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("%s", "Please specify a datastore or nfs target. See -vs usage for examples.")
|
||||
}
|
||||
|
||||
volumeLocations[label] = urlTarget
|
||||
}
|
||||
|
||||
return volumeLocations, nil
|
||||
}
|
||||
|
||||
// processVolumeStoreParam will pull apart the raw input for -vs and return the parts for the actual store that are needed for validation
|
||||
func processVolumeStoreParam(rawVolumeStore string) (*url.URL, string, string, error) {
|
||||
errVolStoreFormat := fmt.Errorf("volume store input must be in format %s or %s", dsInputFormat, nfsInputFormat)
|
||||
splitMeta := strings.Split(rawVolumeStore, ":")
|
||||
if len(splitMeta) < 2 {
|
||||
return nil, "", "", errVolStoreFormat
|
||||
}
|
||||
|
||||
// divide out the label with the target
|
||||
lastIndex := len(splitMeta)
|
||||
label := splitMeta[lastIndex-1]
|
||||
rawTarget := strings.Join(splitMeta[0:lastIndex-1], ":")
|
||||
if label == "" || rawTarget == "" {
|
||||
return nil, "", "", errVolStoreFormat
|
||||
}
|
||||
|
||||
// This case will check if part of the url is assigned as the label (e.g. ds://No.label.target/some/path)
|
||||
if err := CheckUnsupportedChars(label); err != nil {
|
||||
return nil, "", "", errVolStoreFormat
|
||||
}
|
||||
|
||||
// raw target input should be in the form of a url
|
||||
stripRawTarget := rawTarget
|
||||
|
||||
if strings.HasPrefix(stripRawTarget, DsScheme+"://") {
|
||||
stripRawTarget = strings.Replace(rawTarget, DsScheme+"://", "", -1)
|
||||
}
|
||||
|
||||
urlTarget, err := url.Parse(stripRawTarget)
|
||||
if err != nil {
|
||||
return nil, "", "", fmt.Errorf("parsed url for option --volume-store could not be parsed as a url, valid inputs are datastore/path:label or %s. See -h for usage examples.", nfsInputFormat)
|
||||
}
|
||||
|
||||
return urlTarget, rawTarget, label, nil
|
||||
}
|
||||
59
vendor/github.com/vmware/vic/cmd/vic-machine/common/volume_stores_test.go
generated
vendored
Normal file
59
vendor/github.com/vmware/vic/cmd/vic-machine/common/volume_stores_test.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProcessVolumeStoreParam(t *testing.T) {
|
||||
positiveTestCases := []string{
|
||||
"nfs://Shared.Volumes.Org/path/to/store:nfs-volumes",
|
||||
"ds://vsphere.target.here/:root-path",
|
||||
"no.scheme.target:/with/path:ds-store",
|
||||
"looooooooooooooooooooooooooooooong.hoooooooooooooooooooooooooooooooost/short/path:long-check",
|
||||
"nfs://0.0.0.0/ip/check:simple-target",
|
||||
"nfs://prod.shared.storage/vch_prod/volumes:test-label",
|
||||
"ds://0.0.0.0/ip/check?myArg=simple&complex=anotherArg:simple-target:test-label",
|
||||
}
|
||||
|
||||
negativeTestCases := []string{
|
||||
"ds://vsphere.rocks.com/no/label/here",
|
||||
"junk-text-%^()!@#:with-label",
|
||||
"junk-text-%^()!@#-no-label",
|
||||
":no-text",
|
||||
"no-label:",
|
||||
"no-label/with/path",
|
||||
}
|
||||
|
||||
for _, v := range positiveTestCases {
|
||||
target, rawString, label, err := processVolumeStoreParam(v)
|
||||
|
||||
assert.NotNil(t, target, v)
|
||||
assert.NotEqual(t, "", rawString, v)
|
||||
assert.NotEqual(t, "", label, v)
|
||||
assert.Nil(t, err, v)
|
||||
}
|
||||
|
||||
for _, v := range negativeTestCases {
|
||||
target, _, _, err := processVolumeStoreParam(v)
|
||||
|
||||
// here "" is possible for rawString and label so we check for err and nil target.
|
||||
assert.Nil(t, target, v)
|
||||
assert.NotNil(t, err, v)
|
||||
}
|
||||
}
|
||||
470
vendor/github.com/vmware/vic/cmd/vic-machine/configure/configure.go
generated
vendored
Normal file
470
vendor/github.com/vmware/vic/cmd/vic-machine/configure/configure.go
generated
vendored
Normal file
@@ -0,0 +1,470 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package configure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/lib/install/management"
|
||||
"github.com/vmware/vic/lib/install/validate"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// Configure has all input parameters for vic-machine configure command
|
||||
type Configure struct {
|
||||
*data.Data
|
||||
|
||||
proxies common.Proxies
|
||||
cNetworks common.CNetworks
|
||||
dns common.DNS
|
||||
volStores common.VolumeStores
|
||||
registries common.Registries
|
||||
|
||||
certificates common.CertFactory
|
||||
|
||||
upgrade bool
|
||||
executor *management.Dispatcher
|
||||
|
||||
Force bool
|
||||
}
|
||||
|
||||
func NewConfigure() *Configure {
|
||||
configure := &Configure{}
|
||||
configure.Data = data.NewData()
|
||||
|
||||
return configure
|
||||
}
|
||||
|
||||
// Flags return all cli flags for configure
|
||||
func (c *Configure) Flags() []cli.Flag {
|
||||
util := []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "Force the configure operation",
|
||||
Destination: &c.Force,
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Value: 3 * time.Minute,
|
||||
Usage: "Time to wait for configure",
|
||||
Destination: &c.Timeout,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "reset-progress",
|
||||
Usage: "Reset the UpdateInProgress flag. Warning: Do not reset this flag if another upgrade/configure process is running",
|
||||
Destination: &c.ResetInProgressFlag,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "rollback",
|
||||
Usage: "Roll back VCH configuration to before the current upgrade/configure",
|
||||
Destination: &c.Rollback,
|
||||
Hidden: true,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "upgrade",
|
||||
Usage: "Upgrade VCH to latest version together with configure",
|
||||
Destination: &c.upgrade,
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
|
||||
dns := c.dns.DNSFlags(false)
|
||||
target := c.TargetFlags()
|
||||
ops := c.OpsCredentials.Flags(false)
|
||||
id := c.IDFlags()
|
||||
volume := c.volStores.Flags()
|
||||
compute := c.ComputeFlags()
|
||||
container := c.ContainerFlags()
|
||||
debug := c.DebugFlags(false)
|
||||
cNetwork := c.cNetworks.CNetworkFlags(false)
|
||||
proxies := c.proxies.ProxyFlags(false)
|
||||
memory := c.VCHMemoryLimitFlags(false)
|
||||
cpu := c.VCHCPULimitFlags(false)
|
||||
certificates := c.certificates.CertFlags()
|
||||
registries := c.registries.Flags()
|
||||
|
||||
// flag arrays are declared, now combined
|
||||
var flags []cli.Flag
|
||||
for _, f := range [][]cli.Flag{target, ops, id, compute, container, volume, dns, cNetwork, memory, cpu, certificates, registries, proxies, util, debug} {
|
||||
flags = append(flags, f...)
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func (c *Configure) processParams(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if err := c.HasCredentials(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
if c.DNS, err = c.dns.ProcessDNSServers(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hproxy, sproxy, err := c.proxies.ProcessProxies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.HTTPProxy = hproxy
|
||||
c.HTTPSProxy = sproxy
|
||||
c.ProxyIsSet = c.proxies.IsSet
|
||||
|
||||
c.ContainerNetworks, err = c.cNetworks.ProcessContainerNetworks(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Pass empty admin credentials because they are needed only for a create
|
||||
// operation for use as ops credentials if ops credentials are not supplied.
|
||||
if err := c.OpsCredentials.ProcessOpsCredentials(op, false, "", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.VolumeLocations, err = c.volStores.ProcessVolumeStores()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.registries.ProcessRegistries(op); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Data.RegistryCAs = c.registries.RegistryCAs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyChangedConf takes the mostly-empty new config and copies it to the old one. NOTE: o gets installed on the VCH, not n
|
||||
// Currently we cannot automatically override old configuration with any difference in the new configuration, because some options are set during the VCH
|
||||
// Creation process, for example, image store path, volume store path, network slot id, etc. So we'll copy changes based on user input
|
||||
func (c *Configure) copyChangedConf(o *config.VirtualContainerHostConfigSpec, n *config.VirtualContainerHostConfigSpec) {
|
||||
//TODO: copy changed data
|
||||
personaSession := o.ExecutorConfig.Sessions[config.PersonaService]
|
||||
vicAdminSession := o.ExecutorConfig.Sessions[config.VicAdminService]
|
||||
if c.proxies.IsSet {
|
||||
hProxy := ""
|
||||
if c.HTTPProxy != nil {
|
||||
hProxy = c.HTTPProxy.String()
|
||||
}
|
||||
sProxy := ""
|
||||
if c.HTTPSProxy != nil {
|
||||
sProxy = c.HTTPSProxy.String()
|
||||
}
|
||||
updateSessionEnv(personaSession, config.GeneralHTTPProxy, hProxy)
|
||||
updateSessionEnv(personaSession, config.GeneralHTTPSProxy, sProxy)
|
||||
updateSessionEnv(vicAdminSession, config.VICAdminHTTPProxy, hProxy)
|
||||
updateSessionEnv(vicAdminSession, config.VICAdminHTTPSProxy, sProxy)
|
||||
}
|
||||
|
||||
if c.Debug.Debug != nil {
|
||||
o.SetDebug(n.Diagnostics.DebugLevel)
|
||||
}
|
||||
|
||||
if c.cNetworks.IsSet {
|
||||
o.ContainerNetworks = n.ContainerNetworks
|
||||
}
|
||||
|
||||
if c.Data.ContainerNameConvention != "" {
|
||||
o.ContainerNameConvention = c.Data.ContainerNameConvention
|
||||
}
|
||||
|
||||
// Copy the new volume store configuration directly since it has the merged
|
||||
// volume store configuration and its datastore URL fields have been populated
|
||||
// correctly by the storage validator. The old configuration has raw fields.
|
||||
o.VolumeLocations = n.VolumeLocations
|
||||
|
||||
if c.OpsCredentials.IsSet {
|
||||
o.Username = n.Username
|
||||
o.Token = n.Token
|
||||
}
|
||||
|
||||
// Copy the thumbprint directly since it has already been validated.
|
||||
o.TargetThumbprint = n.TargetThumbprint
|
||||
|
||||
if c.dns.IsSet {
|
||||
for k, v := range o.ExecutorConfig.Networks {
|
||||
v.Network.Nameservers = n.ExecutorConfig.Networks[k].Network.Nameservers
|
||||
var gw net.IPNet
|
||||
v.Network.Assigned.Gateway = gw
|
||||
v.Network.Assigned.Nameservers = nil
|
||||
}
|
||||
}
|
||||
|
||||
if n.HostCertificate != nil {
|
||||
o.HostCertificate = n.HostCertificate
|
||||
}
|
||||
|
||||
if n.CertificateAuthorities != nil {
|
||||
o.CertificateAuthorities = n.CertificateAuthorities
|
||||
}
|
||||
|
||||
if n.UserCertificates != nil {
|
||||
o.UserCertificates = n.UserCertificates
|
||||
}
|
||||
|
||||
if n.RegistryCertificateAuthorities != nil {
|
||||
o.RegistryCertificateAuthorities = n.RegistryCertificateAuthorities
|
||||
}
|
||||
}
|
||||
|
||||
func updateSessionEnv(sess *executor.SessionConfig, envName, envValue string) {
|
||||
envs := sess.Cmd.Env
|
||||
var newEnvs []string
|
||||
for _, env := range envs {
|
||||
if strings.HasPrefix(env, envName+"=") {
|
||||
continue
|
||||
}
|
||||
newEnvs = append(newEnvs, env)
|
||||
}
|
||||
if envValue != "" {
|
||||
newEnvs = append(newEnvs, fmt.Sprintf("%s=%s", envName, envValue))
|
||||
}
|
||||
sess.Cmd.Env = newEnvs
|
||||
}
|
||||
|
||||
func (c *Configure) processCertificates(op trace.Operation, client, public, management data.NetworkConfig) error {
|
||||
|
||||
if !c.certificates.NoTLSverify && (c.certificates.Skey == "" || c.certificates.Scert == "") {
|
||||
op.Info("No certificate regeneration requested. No new certificates provided. Certificates left unchanged.")
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.certificates.CertPath == "" {
|
||||
c.certificates.CertPath = c.DisplayName
|
||||
}
|
||||
|
||||
_, err := os.Lstat(c.certificates.CertPath)
|
||||
if err == nil || os.IsExist(err) {
|
||||
return fmt.Errorf("Specified or default certificate output location \"%s\" already exists. Specify a location that does not yet exist with --tls-cert-path to continue or do not specify --tls-noverify if, instead, you want to load certificates from %s", c.certificates.CertPath, c.certificates.CertPath)
|
||||
}
|
||||
|
||||
var debug int
|
||||
if c.Debug.Debug == nil {
|
||||
debug = 0
|
||||
} else {
|
||||
debug = *c.Debug.Debug
|
||||
}
|
||||
|
||||
c.certificates.Networks = common.Networks{
|
||||
ClientNetworkName: client.Name,
|
||||
ClientNetworkIP: client.IP.String(),
|
||||
PublicNetworkName: public.Name,
|
||||
PublicNetworkIP: public.IP.String(),
|
||||
ManagementNetworkName: management.Name,
|
||||
ManagementNetworkIP: management.IP.String(),
|
||||
}
|
||||
|
||||
if err := c.certificates.ProcessCertificates(op, c.DisplayName, c.Force, debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.KeyPEM = c.certificates.KeyPEM
|
||||
c.CertPEM = c.certificates.CertPEM
|
||||
c.ClientCAs = c.certificates.ClientCAs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Configure) Run(clic *cli.Context) (err error) {
|
||||
parentOp := common.NewOperation(clic, c.Debug.Debug)
|
||||
defer func(op trace.Operation) {
|
||||
// urfave/cli will print out exit in error handling, so no more information in main method can be printed out.
|
||||
err = common.LogErrorIfAny(op, clic, err)
|
||||
}(parentOp)
|
||||
op, cancel := trace.WithTimeout(&parentOp, c.Timeout, clic.App.Name)
|
||||
defer cancel()
|
||||
defer func() {
|
||||
if op.Err() != nil && op.Err() == context.DeadlineExceeded {
|
||||
//context deadline exceeded, replace returned error message
|
||||
err = errors.Errorf("Configure timed out: use --timeout to add more time")
|
||||
}
|
||||
}()
|
||||
|
||||
// process input parameters, this should reuse same code with create command, to make sure same options are provided
|
||||
if err = c.processParams(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(clic.Args()) > 0 {
|
||||
op.Errorf("Unknown argument: %s", clic.Args()[0])
|
||||
return errors.New("invalid CLI arguments")
|
||||
}
|
||||
|
||||
// TODO: add additional parameter processing, reuse same code with create command as well
|
||||
|
||||
if c.upgrade {
|
||||
// verify upgrade required parameters here
|
||||
}
|
||||
|
||||
op.Infof("### Configuring VCH ####")
|
||||
|
||||
validator, err := validate.NewValidator(op, c.Data)
|
||||
if err != nil {
|
||||
op.Errorf("Configuring cannot continue - failed to create validator: %s", err)
|
||||
return errors.New("configure failed")
|
||||
}
|
||||
defer validator.Session.Logout(parentOp) // parentOp is used here to ensure the logout occurs, even in the event of timeout
|
||||
|
||||
_, err = validator.ValidateTarget(op, c.Data)
|
||||
if err != nil {
|
||||
op.Errorf("Configuring cannot continue - target validation failed: %s", err)
|
||||
return errors.New("configure failed")
|
||||
}
|
||||
executor := management.NewDispatcher(validator.Context, validator.Session, nil, c.Force)
|
||||
|
||||
var vch *vm.VirtualMachine
|
||||
if c.Data.ID != "" {
|
||||
vch, err = executor.NewVCHFromID(c.Data.ID)
|
||||
} else {
|
||||
vch, err = executor.NewVCHFromComputePath(c.Data.ComputeResourcePath, c.Data.DisplayName, validator)
|
||||
}
|
||||
if err != nil {
|
||||
op.Errorf("Failed to get Virtual Container Host %s", c.DisplayName)
|
||||
op.Error(err)
|
||||
return errors.New("configure failed")
|
||||
}
|
||||
|
||||
op.Info("")
|
||||
op.Infof("VCH ID: %s", vch.Reference().String())
|
||||
|
||||
if c.ResetInProgressFlag {
|
||||
if err = vch.SetVCHUpdateStatus(op, false); err != nil {
|
||||
op.Error("Failed to reset UpdateInProgress flag")
|
||||
op.Error(err)
|
||||
return errors.New("configure failed")
|
||||
}
|
||||
op.Info("Reset UpdateInProgress flag successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
var vchConfig *config.VirtualContainerHostConfigSpec
|
||||
if c.upgrade {
|
||||
vchConfig, err = executor.FetchAndMigrateVCHConfig(vch)
|
||||
} else {
|
||||
vchConfig, err = executor.GetVCHConfig(vch)
|
||||
}
|
||||
if err != nil {
|
||||
op.Error("Failed to get Virtual Container Host configuration")
|
||||
op.Error(err)
|
||||
return errors.New("configure failed")
|
||||
}
|
||||
|
||||
installerVer := version.GetBuild().PluginVersion
|
||||
if vchConfig.ExecutorConfig.Version == nil {
|
||||
op.Error("Cannot configure VCH with version unavailable")
|
||||
return errors.New("configure failed")
|
||||
}
|
||||
if vchConfig.ExecutorConfig.Version.PluginVersion < installerVer {
|
||||
op.Errorf("Cannot configure VCH with version %s, please upgrade first", vchConfig.ExecutorConfig.Version.ShortVersion())
|
||||
return errors.New("configure failed")
|
||||
}
|
||||
|
||||
// Convert guestinfo *VirtualContainerHost back to *Data, decrypt secret data
|
||||
oldData, err := validate.NewDataFromConfig(op, validator.Session.Finder, vchConfig)
|
||||
if err != nil {
|
||||
op.Error("Configuring cannot continue: configuration conversion failed")
|
||||
op.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = validate.SetDataFromVM(op, validator.Session.Finder, vch, oldData); err != nil {
|
||||
op.Error("Configuring cannot continue: querying configuration from VM failed")
|
||||
op.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// using new configuration override configuration query from guestinfo
|
||||
if err = oldData.CopyNonEmpty(c.Data); err != nil {
|
||||
op.Error("Configuring cannot continue: copying configuration failed")
|
||||
return err
|
||||
}
|
||||
c.Data = oldData
|
||||
|
||||
// in Create we process certificates as part of processParams but we need the old conf
|
||||
// to do this in the context of Configure so we need to call this method here instead
|
||||
if err = c.processCertificates(op, c.Data.ClientNetwork, c.Data.PublicNetwork, c.Data.ManagementNetwork); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// evaluate merged configuration
|
||||
newConfig, err := validator.Validate(op, c.Data)
|
||||
if err != nil {
|
||||
op.Error("Configuring cannot continue: configuration validation failed")
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: copy changed configuration here. https://github.com/vmware/vic/issues/2911
|
||||
c.copyChangedConf(vchConfig, newConfig)
|
||||
|
||||
vConfig := validator.AddDeprecatedFields(op, vchConfig, c.Data)
|
||||
vConfig.Timeout = c.Timeout
|
||||
vConfig.VCHSizeIsSet = c.ResourceLimits.IsSet
|
||||
|
||||
updating, err := vch.VCHUpdateStatus(op)
|
||||
if err != nil {
|
||||
op.Error("Unable to determine if upgrade/configure is in progress")
|
||||
op.Error(err)
|
||||
return errors.New("configure failed")
|
||||
}
|
||||
if updating {
|
||||
op.Error("Configure failed: another upgrade/configure operation is in progress")
|
||||
op.Error("If no other upgrade/configure process is running, use --reset-progress to reset the VCH upgrade/configure status")
|
||||
return errors.New("configure failed")
|
||||
}
|
||||
|
||||
if err = vch.SetVCHUpdateStatus(op, true); err != nil {
|
||||
op.Error("Failed to set UpdateInProgress flag to true")
|
||||
op.Error(err)
|
||||
return errors.New("configure failed")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err = vch.SetVCHUpdateStatus(op, false); err != nil {
|
||||
op.Error("Failed to reset UpdateInProgress")
|
||||
op.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if !c.Data.Rollback {
|
||||
err = executor.Configure(vch, vchConfig, vConfig, true)
|
||||
} else {
|
||||
err = executor.Rollback(vch, vchConfig, vConfig)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// configure failed
|
||||
executor.CollectDiagnosticLogs()
|
||||
return errors.New("configure failed")
|
||||
}
|
||||
|
||||
op.Info("Completed successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
576
vendor/github.com/vmware/vic/cmd/vic-machine/converter/converter.go
generated
vendored
Normal file
576
vendor/github.com/vmware/vic/cmd/vic-machine/converter/converter.go
generated
vendored
Normal file
@@ -0,0 +1,576 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package converter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/pkg/ip"
|
||||
viclog "github.com/vmware/vic/pkg/log"
|
||||
)
|
||||
|
||||
const (
|
||||
cmdTag = "cmd"
|
||||
labelTag = "label"
|
||||
parentTagValue = "parent"
|
||||
|
||||
optionSeparator = "-"
|
||||
|
||||
keyAfterValueLabel = "value-key"
|
||||
valueAfterKeyLabel = "key-value"
|
||||
)
|
||||
|
||||
var (
|
||||
kindConverters = make(map[reflect.Kind]converter)
|
||||
typeConverters = make(map[string]converter)
|
||||
labelHandlers = make(map[string]labelConverter)
|
||||
)
|
||||
|
||||
var log = &logrus.Logger{
|
||||
Out: os.Stderr,
|
||||
// We're using our own text formatter to skip the \n and \t escaping logrus
|
||||
// was doing on non TTY Out (we redirect to a file) descriptors.
|
||||
Formatter: viclog.NewTextFormatter(),
|
||||
Hooks: make(logrus.LevelHooks),
|
||||
Level: logrus.InfoLevel,
|
||||
}
|
||||
|
||||
type converter func(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error
|
||||
type labelConverter func(dest map[string][]string, key string) error
|
||||
|
||||
func init() {
|
||||
kindConverters[reflect.Struct] = convertStruct
|
||||
kindConverters[reflect.Slice] = convertSlice
|
||||
kindConverters[reflect.Map] = convertMap
|
||||
kindConverters[reflect.String] = convertString
|
||||
kindConverters[reflect.Ptr] = convertPtr
|
||||
kindConverters[reflect.Int] = convertPrimitive
|
||||
kindConverters[reflect.Int8] = convertPrimitive
|
||||
kindConverters[reflect.Int16] = convertPrimitive
|
||||
kindConverters[reflect.Int32] = convertPrimitive
|
||||
kindConverters[reflect.Int64] = convertPrimitive
|
||||
kindConverters[reflect.Bool] = convertPrimitive
|
||||
kindConverters[reflect.Float32] = convertPrimitive
|
||||
kindConverters[reflect.Float64] = convertPrimitive
|
||||
|
||||
typeConverters["url.URL"] = convertURL
|
||||
typeConverters["net.IPNet"] = convertIPNet
|
||||
typeConverters["net.IP"] = convertIP
|
||||
typeConverters["ip.Range"] = convertIPRange
|
||||
typeConverters["data.NetworkConfig"] = convertNetwork
|
||||
typeConverters["common.ContainerNetworks"] = convertContainerNetworks
|
||||
typeConverters["executor.TrustLevel"] = convertTrustLevel
|
||||
typeConverters["types.SharesInfo"] = convertShares
|
||||
|
||||
labelHandlers[keyAfterValueLabel] = keyAfterValueLabelHandler
|
||||
labelHandlers[valueAfterKeyLabel] = valueAfterKeyLabelHandler
|
||||
}
|
||||
|
||||
func EnableLog() {
|
||||
log.Level = logrus.DebugLevel
|
||||
}
|
||||
|
||||
func DisableLog() {
|
||||
log.Level = logrus.InfoLevel
|
||||
}
|
||||
|
||||
// DataToOption convert data.Data structure to vic-machine create command options based on tags defined in data.Data structure
|
||||
// Note: need to make sure the tags are consistent with command line option name
|
||||
func DataToOption(data *data.Data) (map[string][]string, error) {
|
||||
result := make(map[string][]string)
|
||||
|
||||
if data == nil {
|
||||
return result, nil
|
||||
}
|
||||
err := convert(reflect.ValueOf(data), "", "", result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func convert(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
t := src.Type().String()
|
||||
if converter, ok := typeConverters[t]; ok {
|
||||
return converter(src, prefix, tags, dest)
|
||||
|
||||
}
|
||||
if converter, ok := kindConverters[src.Kind()]; ok {
|
||||
return converter(src, prefix, tags, dest)
|
||||
}
|
||||
log.Debugf("Skipping unsupported field, interface: %#v, kind %s", src, src.Kind())
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertPtr(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
if src.IsNil() {
|
||||
// no need to attempt anything
|
||||
return nil
|
||||
}
|
||||
return convert(src.Elem(), prefix, tags, dest)
|
||||
}
|
||||
|
||||
func convertStruct(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertStruct: prefix: %s, src: %s", prefix, src.String())
|
||||
|
||||
// iterate through every field in the struct
|
||||
for i := 0; i < src.NumField(); i++ {
|
||||
field := src.Field(i)
|
||||
// get field key, and keep going even if the attribute key is empty, to make sure children attribute is not missing
|
||||
tags := src.Type().Field(i).Tag
|
||||
key := calculateKey(tags, prefix)
|
||||
if err := convert(field, key, tags, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if field.Kind() == reflect.Map {
|
||||
// label handler is invoked in map converter
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertSlice(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertSlice: prefix: %s, src: %s", prefix, src)
|
||||
|
||||
length := src.Len()
|
||||
if length == 0 {
|
||||
log.Debug("Skipping empty slice")
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
if err := convert(src.Index(i), prefix, tags, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertMap(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertMap: prefix: %s, src: %s", prefix, src)
|
||||
|
||||
// iterate over keys and recurse
|
||||
mkeys := src.MapKeys()
|
||||
length := len(mkeys)
|
||||
if length == 0 {
|
||||
log.Debug("Skipping empty map")
|
||||
return nil
|
||||
}
|
||||
|
||||
handler, hasHandler := labelHandlers[tags.Get(labelTag)]
|
||||
// use tempMap to avoid duplicate processing
|
||||
for _, pkey := range src.MapKeys() {
|
||||
tempMap := make(map[string][]string)
|
||||
if pkey.Kind() != reflect.String {
|
||||
log.Errorf("Unsupported map key type interface: %s, kind %s", src, src.Kind())
|
||||
continue
|
||||
}
|
||||
if !hasHandler {
|
||||
if err := convert(src.MapIndex(pkey), prefix, tags, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := convert(src.MapIndex(pkey), prefix, tags, tempMap); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := handler(tempMap, pkey.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range tempMap {
|
||||
addValues(dest, k, v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// keyAfterValueLabelHandler will add the map key as label after the value,
|
||||
// e.g. change from datastore/path to datastore/path:default
|
||||
func keyAfterValueLabelHandler(dest map[string][]string, pkey string) error {
|
||||
log.Debugf("keyAfterValueLabelHandler: map key: %s, map: %#v", pkey, dest)
|
||||
|
||||
for _, values := range dest {
|
||||
for i := range values {
|
||||
values[i] = fmt.Sprintf("%s:%s", values[i], pkey)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// valueAfterKeyLabelHandler will add the map key as label before the value,
|
||||
// e.g. change from 10.10.10.0/24 to management:10.10.10.0/24
|
||||
func valueAfterKeyLabelHandler(dest map[string][]string, pkey string) error {
|
||||
log.Debugf("valueAfterKeyLabelHandler: map key: %s, map: %#v", pkey, dest)
|
||||
|
||||
for _, values := range dest {
|
||||
for i := range values {
|
||||
values[i] = fmt.Sprintf("%s:%s", pkey, values[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// calculateKey generate key as prefix-tag. if any one is empty, return the other
|
||||
func calculateKey(tags reflect.StructTag, prefix string) string {
|
||||
tag := tags.Get(cmdTag)
|
||||
if tag == "" {
|
||||
return prefix
|
||||
}
|
||||
if tag == parentTagValue && prefix == "" {
|
||||
return ""
|
||||
}
|
||||
if tag == parentTagValue && prefix != "" {
|
||||
// for this tag, use parent name only
|
||||
return prefix
|
||||
}
|
||||
if prefix == "" {
|
||||
return tag
|
||||
}
|
||||
return fmt.Sprintf("%s%s%s", prefix, optionSeparator, tag)
|
||||
}
|
||||
|
||||
func convertURL(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertURL: prefix: %s, src: %s", prefix, src.String())
|
||||
|
||||
if prefix == "" {
|
||||
return nil
|
||||
}
|
||||
if tags.Get(cmdTag) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
u, ok := src.Interface().(url.URL)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(src.Type().String() + " is not URL"))
|
||||
}
|
||||
v := u.String()
|
||||
if u.Scheme == "" {
|
||||
if u.Path == "" {
|
||||
v = u.Host
|
||||
} else if u.Host == "" {
|
||||
v = u.Path
|
||||
} else {
|
||||
v = fmt.Sprintf("%s/%s", u.Host, u.Path)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("%s=%s", prefix, v)
|
||||
addValue(dest, prefix, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertIPNet(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertIPNet: prefix: %s, src: %s", prefix, src.String())
|
||||
|
||||
if prefix == "" {
|
||||
return nil
|
||||
}
|
||||
if tags.Get(cmdTag) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ipNet, ok := src.Interface().(net.IPNet)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(src.Type().String() + " is not IPNet"))
|
||||
}
|
||||
if ip.IsUnspecifiedSubnet(&ipNet) {
|
||||
return nil
|
||||
}
|
||||
v := ipNet.String()
|
||||
|
||||
log.Debugf("%s=%s", prefix, v)
|
||||
addValue(dest, prefix, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertIP(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertIP: prefix: %s, src: %s", prefix, src.String())
|
||||
|
||||
if prefix == "" {
|
||||
return nil
|
||||
}
|
||||
if tags.Get(cmdTag) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ipAddr, ok := src.Interface().(net.IP)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(src.Type().String() + " is not IP"))
|
||||
}
|
||||
if ip.IsUnspecifiedIP(ipAddr) {
|
||||
return nil
|
||||
}
|
||||
v := ipAddr.String()
|
||||
|
||||
log.Debugf("%s=%s", prefix, v)
|
||||
addValue(dest, prefix, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertTrustLevel(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertTrustLevel: prefix: %s, src: %s", prefix, src.String())
|
||||
trustLevel, ok := src.Interface().(executor.TrustLevel)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(src.Type().String() + " is not TrustLevel"))
|
||||
}
|
||||
if trustLevel == executor.Unspecified {
|
||||
return nil
|
||||
}
|
||||
v := trustLevel.String()
|
||||
|
||||
log.Debugf("%s=%s", prefix, v)
|
||||
addValue(dest, prefix, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertIPRange(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertIPRange: prefix: %s, src: %s", prefix, src.String())
|
||||
|
||||
if prefix == "" {
|
||||
return nil
|
||||
}
|
||||
if tags.Get(cmdTag) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ipRange, ok := src.Interface().(ip.Range)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(src.Type().String() + " is not ip range"))
|
||||
}
|
||||
v := ipRange.String()
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("%s=%s", prefix, v)
|
||||
addValue(dest, prefix, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertString(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertString: prefix: %s, src: %s", prefix, src.String())
|
||||
|
||||
if prefix == "" {
|
||||
return nil
|
||||
}
|
||||
if tags.Get(cmdTag) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := src.String()
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("%s=%s", prefix, v)
|
||||
|
||||
addValue(dest, prefix, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertPrimitive(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertPrimitive: prefix: %s, src: %s", prefix, src.String())
|
||||
|
||||
if prefix == "" {
|
||||
return nil
|
||||
}
|
||||
if tags.Get(cmdTag) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := ""
|
||||
switch src.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if src.Int() == 0 {
|
||||
return nil
|
||||
}
|
||||
v = strconv.FormatInt(src.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if src.Uint() == 0 {
|
||||
return nil
|
||||
}
|
||||
v = strconv.FormatUint(src.Uint(), 10)
|
||||
case reflect.Bool:
|
||||
if !src.Bool() {
|
||||
return nil
|
||||
}
|
||||
v = strconv.FormatBool(src.Bool())
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if src.Float() == 0 {
|
||||
return nil
|
||||
}
|
||||
v = strconv.FormatFloat(src.Float(), 'E', -1, 64)
|
||||
default:
|
||||
panic(fmt.Sprintf("%s is not supported type", src.Kind()))
|
||||
}
|
||||
log.Debugf("%s=%s", prefix, v)
|
||||
|
||||
addValue(dest, prefix, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertNetwork will merge destination and gateway to one option with format: 192.168.3.0/16,192.168.128.0/16:192.168.2.1
|
||||
// after that, convertStruct is called for left conversion
|
||||
func convertNetwork(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertNetwork: prefix: %s, src: %s", prefix, src.String())
|
||||
|
||||
if prefix == "" {
|
||||
return nil
|
||||
}
|
||||
if tags.Get(cmdTag) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
network, ok := src.Interface().(data.NetworkConfig)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(src.Type().String() + " is not NetworkConfig"))
|
||||
}
|
||||
if !network.IsSet() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(network.Destinations) > 0 || !ip.Empty(network.Gateway) {
|
||||
destination := ""
|
||||
if len(network.Destinations) > 0 {
|
||||
for _, d := range network.Destinations {
|
||||
destination = fmt.Sprintf("%s,%s", destination, d.String())
|
||||
}
|
||||
destination = strings.TrimLeft(destination, ",")
|
||||
}
|
||||
gateway := network.Gateway.IP.String()
|
||||
tag := "cmd:\"gateway\""
|
||||
key := calculateKey(reflect.StructTag(tag), prefix)
|
||||
if destination != "" {
|
||||
addValue(dest, key, fmt.Sprintf("%s:%s", destination, gateway))
|
||||
} else {
|
||||
addValue(dest, key, gateway)
|
||||
}
|
||||
}
|
||||
return convertStruct(reflect.ValueOf(network), prefix, tags, dest)
|
||||
}
|
||||
|
||||
func convertShares(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertShares: prefix: %s, src: %s", prefix, src.String())
|
||||
|
||||
if prefix == "" {
|
||||
return nil
|
||||
}
|
||||
if tags.Get(cmdTag) == "" {
|
||||
return nil
|
||||
}
|
||||
shares, ok := src.Interface().(types.SharesInfo)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(src.Type().String() + " is not SharesInfo"))
|
||||
}
|
||||
|
||||
v := ""
|
||||
switch shares.Level {
|
||||
case types.SharesLevelCustom:
|
||||
v = fmt.Sprintf("%v", shares.Shares)
|
||||
case types.SharesLevelNormal:
|
||||
return nil
|
||||
default:
|
||||
v = string(shares.Level)
|
||||
}
|
||||
|
||||
log.Debugf("%s=%s", prefix, v)
|
||||
|
||||
addValue(dest, prefix, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertContainerNetworks will switch the map keys in MappedNetworks using value, and replace all keys with the same value in other structure,
|
||||
// cause option is using vsphere network name as key label, but guestinfo is using alias as key for easy to use in portlayer
|
||||
// after that, convertStruct is called for left conversion
|
||||
func convertContainerNetworks(src reflect.Value, prefix string, tags reflect.StructTag, dest map[string][]string) error {
|
||||
log.Debugf("convertContainerNetworks: prefix: %s, src: %s", prefix, src.String())
|
||||
|
||||
if prefix == "" {
|
||||
return nil
|
||||
}
|
||||
if tags.Get(cmdTag) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
networks, ok := src.Interface().(common.ContainerNetworks)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(src.Type().String() + " is not ContainerNetworks"))
|
||||
}
|
||||
if !networks.IsSet() {
|
||||
return nil
|
||||
}
|
||||
|
||||
newMappedNetworks := make(map[string]string, len(networks.MappedNetworks))
|
||||
for k, v := range networks.MappedNetworks {
|
||||
if k == v {
|
||||
newMappedNetworks[v] = k
|
||||
continue
|
||||
}
|
||||
if dns, ok := networks.MappedNetworksDNS[k]; ok {
|
||||
networks.MappedNetworksDNS[v] = dns
|
||||
delete(networks.MappedNetworksDNS, k)
|
||||
}
|
||||
if gateways, ok := networks.MappedNetworksGateways[k]; ok {
|
||||
networks.MappedNetworksGateways[v] = gateways
|
||||
delete(networks.MappedNetworksGateways, k)
|
||||
}
|
||||
if ipRange, ok := networks.MappedNetworksIPRanges[k]; ok {
|
||||
networks.MappedNetworksIPRanges[v] = ipRange
|
||||
delete(networks.MappedNetworksIPRanges, k)
|
||||
}
|
||||
if firewall, ok := networks.MappedNetworksFirewalls[k]; ok {
|
||||
networks.MappedNetworksFirewalls[v] = firewall
|
||||
delete(networks.MappedNetworksFirewalls, k)
|
||||
}
|
||||
newMappedNetworks[v] = k
|
||||
}
|
||||
networks.MappedNetworks = newMappedNetworks
|
||||
return convertStruct(reflect.ValueOf(networks), prefix, tags, dest)
|
||||
}
|
||||
|
||||
// addValue will append value without duplicates
|
||||
func addValue(dest map[string][]string, key, value string) {
|
||||
slice, _ := dest[key]
|
||||
found := false
|
||||
for _, o := range slice {
|
||||
if o == value {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
slice = append(slice, value)
|
||||
}
|
||||
dest[key] = slice
|
||||
}
|
||||
|
||||
// addValues append new value to existing slice if missing
|
||||
// as this method is called every time the value is appended, the existing slice will be no duplicates
|
||||
func addValues(dest map[string][]string, key string, values []string) {
|
||||
for _, v := range values {
|
||||
addValue(dest, key, v)
|
||||
}
|
||||
}
|
||||
650
vendor/github.com/vmware/vic/cmd/vic-machine/converter/converter_test.go
generated
vendored
Normal file
650
vendor/github.com/vmware/vic/cmd/vic-machine/converter/converter_test.go
generated
vendored
Normal file
@@ -0,0 +1,650 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package converter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/lib/install/validate"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
type testFinder struct {
|
||||
}
|
||||
|
||||
func (f *testFinder) ObjectReference(ctx context.Context, ref types.ManagedObjectReference) (object.Reference, error) {
|
||||
return testObject{
|
||||
Common: object.NewCommon(nil, ref),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type testObject struct {
|
||||
object.Common
|
||||
}
|
||||
|
||||
func (c testObject) Name() string {
|
||||
switch c.Common.Reference().String() {
|
||||
case "DistributedVirtualPortgroup:dvportgroup-357":
|
||||
return "management"
|
||||
case "DistributedVirtualPortgroup:dvportgroup-358":
|
||||
return "vm-network"
|
||||
case "DistributedVirtualPortgroup:dvportgroup-55":
|
||||
return "bridge"
|
||||
case "DistributedVirtualPortgroup:dvportgroup-56":
|
||||
return "external"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
// EnableLog()
|
||||
// trace.Logger.Level = log.DebugLevel
|
||||
|
||||
testInit(t)
|
||||
testConvertImageStore(t)
|
||||
testConvertVolumeStore(t)
|
||||
testConvertVCHName(t)
|
||||
testConvertTarget(t)
|
||||
testConvertOps(t)
|
||||
testConvertBaseImageSize(t)
|
||||
testConvertPublicNetwork(t)
|
||||
testConvertClientNetwork(t)
|
||||
testConvertMgmtNetwork(t)
|
||||
testGuestinfo(t)
|
||||
testConvertSharesInfo(t)
|
||||
}
|
||||
|
||||
func testInit(t *testing.T) {
|
||||
var ok bool
|
||||
_, ok = kindConverters[reflect.Struct]
|
||||
assert.True(t, ok, fmt.Sprintf("Struct converter is not found"))
|
||||
_, ok = kindConverters[reflect.Slice]
|
||||
assert.True(t, ok, fmt.Sprintf("Slice converter is not found"))
|
||||
_, ok = kindConverters[reflect.Map]
|
||||
assert.True(t, ok, fmt.Sprintf("Map converter is not found"))
|
||||
_, ok = kindConverters[reflect.String]
|
||||
assert.True(t, ok, fmt.Sprintf("String converter is not found"))
|
||||
_, ok = kindConverters[reflect.Ptr]
|
||||
assert.True(t, ok, fmt.Sprintf("Pointer converter is not found"))
|
||||
_, ok = kindConverters[reflect.Int]
|
||||
assert.True(t, ok, fmt.Sprintf("Int converter is not found"))
|
||||
_, ok = kindConverters[reflect.Int8]
|
||||
assert.True(t, ok, fmt.Sprintf("Int8 converter is not found"))
|
||||
_, ok = kindConverters[reflect.Int16]
|
||||
assert.True(t, ok, fmt.Sprintf("Int16 converter is not found"))
|
||||
_, ok = kindConverters[reflect.Int32]
|
||||
assert.True(t, ok, fmt.Sprintf("Int32 converter is not found"))
|
||||
_, ok = kindConverters[reflect.Int64]
|
||||
assert.True(t, ok, fmt.Sprintf("Int64 converter is not found"))
|
||||
_, ok = kindConverters[reflect.Bool]
|
||||
assert.True(t, ok, fmt.Sprintf("Bool converter is not found"))
|
||||
_, ok = kindConverters[reflect.Float32]
|
||||
assert.True(t, ok, fmt.Sprintf("Float32 converter is not found"))
|
||||
_, ok = kindConverters[reflect.Float64]
|
||||
assert.True(t, ok, fmt.Sprintf("Float64 converter is not found"))
|
||||
|
||||
_, ok = typeConverters["url.URL"]
|
||||
assert.True(t, ok, fmt.Sprintf("url.URL converter is not found"))
|
||||
_, ok = typeConverters["net.IPNet"]
|
||||
assert.True(t, ok, fmt.Sprintf("net.IPNet converter is not found"))
|
||||
_, ok = typeConverters["net.IP"]
|
||||
assert.True(t, ok, fmt.Sprintf("net.IP converter is not found"))
|
||||
_, ok = typeConverters["ip.Range"]
|
||||
assert.True(t, ok, fmt.Sprintf("ip.Range converter is not found"))
|
||||
_, ok = typeConverters["data.NetworkConfig"]
|
||||
assert.True(t, ok, fmt.Sprintf("data.NetworkConfig converter is not found"))
|
||||
_, ok = typeConverters["common.ContainerNetworks"]
|
||||
assert.True(t, ok, fmt.Sprintf("common.ContainerNetworks converter is not found"))
|
||||
|
||||
_, ok = labelHandlers[keyAfterValueLabel]
|
||||
assert.True(t, ok, fmt.Sprintf("value-key handler is not found"))
|
||||
_, ok = labelHandlers[valueAfterKeyLabel]
|
||||
assert.True(t, ok, fmt.Sprintf("key-value handler is not found"))
|
||||
}
|
||||
|
||||
func testConvertImageStore(t *testing.T) {
|
||||
data := data.NewData()
|
||||
data.ImageDatastorePath = "ds://vsan/path"
|
||||
options, err := DataToOption(data)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, 1, len(options), "should not have other option generated")
|
||||
assert.Equal(t, "ds://vsan/path", options["image-store"][0], "not expected image-store option")
|
||||
}
|
||||
|
||||
func testConvertVolumeStore(t *testing.T) {
|
||||
tests := []struct {
|
||||
in map[string]*url.URL
|
||||
out []string
|
||||
}{
|
||||
{
|
||||
in: map[string]*url.URL{
|
||||
"default": {
|
||||
Scheme: "ds",
|
||||
Host: "vsan",
|
||||
Path: "path/volume",
|
||||
},
|
||||
|
||||
"noSchema": {
|
||||
Host: "vsan",
|
||||
Path: "path1",
|
||||
},
|
||||
},
|
||||
out: []string{
|
||||
"ds://vsan/path/volume:default",
|
||||
"vsan/path1:noSchema",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
data := data.NewData()
|
||||
data.VolumeLocations = test.in
|
||||
options, err := DataToOption(data)
|
||||
assert.Empty(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(options), "should not have other option generated")
|
||||
vols := options["volume-store"]
|
||||
assert.Equal(t, len(test.out), len(vols), "not expected length")
|
||||
for _, store := range test.out {
|
||||
found := false
|
||||
for _, actual := range vols {
|
||||
if actual == store {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, fmt.Sprintf("%s is not created", store))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testConvertVCHName(t *testing.T) {
|
||||
data := data.NewData()
|
||||
data.DisplayName = "vch1"
|
||||
options, err := DataToOption(data)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, 1, len(options), "should not have other option generated")
|
||||
assert.Equal(t, "vch1", options["name"][0], "not expected name option")
|
||||
}
|
||||
|
||||
func testConvertTarget(t *testing.T) {
|
||||
data := data.NewData()
|
||||
data.Target.URL, _ = url.Parse("1.1.1.1")
|
||||
options, err := DataToOption(data)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, 1, len(options), "should not have other option generated")
|
||||
assert.Equal(t, "1.1.1.1", options["target"][0], "not expected target option")
|
||||
}
|
||||
|
||||
func testConvertOps(t *testing.T) {
|
||||
data := data.NewData()
|
||||
adminUser := "admin"
|
||||
data.OpsCredentials.OpsUser = &adminUser
|
||||
pass := "password"
|
||||
data.OpsCredentials.OpsPassword = &pass
|
||||
data.Thumbprint = "uuidstring"
|
||||
options, err := DataToOption(data)
|
||||
assert.Empty(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(options), "should not have other option generated")
|
||||
assert.Equal(t, *data.OpsCredentials.OpsUser, options["ops-user"][0], "not expected ops-user option")
|
||||
assert.Equal(t, data.Thumbprint, options["thumbprint"][0], "not expected thumbprint option")
|
||||
}
|
||||
|
||||
func testConvertBaseImageSize(t *testing.T) {
|
||||
data := data.NewData()
|
||||
data.ScratchSize = "8GB"
|
||||
options, err := DataToOption(data)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, 1, len(options), "should not have other option generated")
|
||||
assert.Equal(t, data.ScratchSize, options["base-image-size"][0], "not expected base-image-size option")
|
||||
}
|
||||
|
||||
func testConvertBridgeNetwork(t *testing.T) {
|
||||
_, ipnet, _ := net.ParseCIDR("172.16.0.0/12")
|
||||
|
||||
data := data.NewData()
|
||||
data.BridgeIPRange = ipnet
|
||||
data.BridgeNetworkName = "portgroup1"
|
||||
options, err := DataToOption(data)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, 2, len(options), "should not have other option generated")
|
||||
assert.Equal(t, "172.16.0.0/12", options["bridge-network-range"][0], "not expected bridge-network-range option")
|
||||
assert.Equal(t, data.BridgeNetworkName, options["bridge-network"][0], "not expected bridge-network option")
|
||||
}
|
||||
|
||||
func testConvertPublicNetwork(t *testing.T) {
|
||||
gwIP := net.ParseIP("172.16.0.1")
|
||||
_, destination, _ := net.ParseCIDR("172.16.0.0/12")
|
||||
ip, ipNet, _ := net.ParseCIDR("172.16.0.2/12")
|
||||
|
||||
data := data.NewData()
|
||||
data.PublicNetwork.Name = "public"
|
||||
data.PublicNetwork.Gateway.IP = gwIP
|
||||
data.PublicNetwork.Gateway.Mask = ipNet.Mask
|
||||
data.PublicNetwork.IP.IP = ip
|
||||
data.PublicNetwork.IP.Mask = ipNet.Mask
|
||||
data.PublicNetwork.Destinations = append(data.PublicNetwork.Destinations, *destination)
|
||||
|
||||
options, err := DataToOption(data)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, 3, len(options), "should not have other option generated")
|
||||
assert.Equal(t, "172.16.0.0/12:172.16.0.1", options["public-network-gateway"][0], "not expected public-network-gateway option")
|
||||
assert.Equal(t, "172.16.0.2/12", options["public-network-ip"][0], "not expected public-network-ip option")
|
||||
}
|
||||
|
||||
func testConvertClientNetwork(t *testing.T) {
|
||||
gwIP := net.ParseIP("172.16.0.1")
|
||||
_, destination, _ := net.ParseCIDR("172.16.0.0/12")
|
||||
ip, ipNet, _ := net.ParseCIDR("172.16.0.2/12")
|
||||
|
||||
data := data.NewData()
|
||||
data.ClientNetwork.Name = "public"
|
||||
data.ClientNetwork.Gateway.IP = gwIP
|
||||
data.ClientNetwork.Gateway.Mask = ipNet.Mask
|
||||
data.ClientNetwork.IP.IP = ip
|
||||
data.ClientNetwork.IP.Mask = ipNet.Mask
|
||||
data.ClientNetwork.Destinations = append(data.ClientNetwork.Destinations, *destination)
|
||||
|
||||
options, err := DataToOption(data)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, 3, len(options), "should not have other option generated")
|
||||
assert.Equal(t, "172.16.0.0/12:172.16.0.1", options["client-network-gateway"][0], "not expected client-network-gateway option")
|
||||
assert.Equal(t, "172.16.0.2/12", options["client-network-ip"][0], "not expected client-network-ip option")
|
||||
}
|
||||
|
||||
func testConvertMgmtNetwork(t *testing.T) {
|
||||
gwIP := net.ParseIP("172.16.0.1")
|
||||
_, destination, _ := net.ParseCIDR("172.16.0.0/12")
|
||||
ip, ipNet, _ := net.ParseCIDR("172.16.0.2/12")
|
||||
|
||||
data := data.NewData()
|
||||
data.ManagementNetwork.Name = "public"
|
||||
data.ManagementNetwork.Gateway.IP = gwIP
|
||||
data.ManagementNetwork.Gateway.Mask = ipNet.Mask
|
||||
data.ManagementNetwork.IP.IP = ip
|
||||
data.ManagementNetwork.IP.Mask = ipNet.Mask
|
||||
data.ManagementNetwork.Destinations = append(data.ManagementNetwork.Destinations, *destination)
|
||||
|
||||
options, err := DataToOption(data)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, 3, len(options), "should not have other option generated")
|
||||
assert.Equal(t, "172.16.0.0/12:172.16.0.1", options["management-network-gateway"][0], "not expected management-network-gateway option")
|
||||
assert.Equal(t, "172.16.0.2/12", options["management-network-ip"][0], "not expected management-network-ip option")
|
||||
}
|
||||
|
||||
func testConvertContainerNetworks(t *testing.T) {
|
||||
gwIP := net.ParseIP("172.16.0.1")
|
||||
_, destination, _ := net.ParseCIDR("172.16.0.0/12")
|
||||
ip, ipNet, _ := net.ParseCIDR("172.16.0.2/12")
|
||||
|
||||
data := data.NewData()
|
||||
data.ManagementNetwork.Name = "public"
|
||||
data.ManagementNetwork.Gateway.IP = gwIP
|
||||
data.ManagementNetwork.Gateway.Mask = ipNet.Mask
|
||||
data.ManagementNetwork.IP.IP = ip
|
||||
data.ManagementNetwork.IP.Mask = ipNet.Mask
|
||||
data.ManagementNetwork.Destinations = append(data.ManagementNetwork.Destinations, *destination)
|
||||
|
||||
options, err := DataToOption(data)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, 3, len(options), "should not have other option generated")
|
||||
assert.Equal(t, "172.16.0.0/12:172.16.0.1", options["management-network-gateway"][0], "not expected management-network-gateway option")
|
||||
assert.Equal(t, "172.16.0.2/12", options["management-network-ip"][0], "not expected management-network-ip option")
|
||||
}
|
||||
|
||||
func testConvertSharesInfo(t *testing.T) {
|
||||
data := data.NewData()
|
||||
cLimit, cReserve := 29300, 1024
|
||||
data.NumCPUs = 2
|
||||
data.VCHCPULimitsMHz = &cLimit
|
||||
data.VCHCPUReservationsMHz = &cReserve
|
||||
data.VCHCPUShares = &types.SharesInfo{
|
||||
Shares: 6000,
|
||||
Level: types.SharesLevelCustom,
|
||||
}
|
||||
mLimit, mReserve := 13144, 1024
|
||||
data.MemoryMB = 4096
|
||||
data.VCHMemoryLimitsMB = &mLimit
|
||||
data.VCHMemoryReservationsMB = &mReserve
|
||||
data.VCHMemoryShares = &types.SharesInfo{
|
||||
Shares: 163840,
|
||||
Level: types.SharesLevelNormal,
|
||||
}
|
||||
|
||||
options, err := DataToOption(data)
|
||||
assert.Empty(t, err)
|
||||
assert.Equal(t, 7, len(options), "should not have other option generated")
|
||||
assert.Equal(t, "2", options["endpoint-cpu"][0], "not expected endpoint-cpu option")
|
||||
assert.Equal(t, "4096", options["endpoint-memory"][0], "not expected endpoint-memory option")
|
||||
assert.Equal(t, "13144", options["memory"][0], "not expected memory option")
|
||||
assert.Equal(t, "1024", options["memory-reservation"][0], "not expected memory-reservation option")
|
||||
assert.Equal(t, "29300", options["cpu"][0], "not expected cpu option")
|
||||
assert.Equal(t, "1024", options["cpu-reservation"][0], "not expected cpu-reservation option")
|
||||
assert.Equal(t, "6000", options["cpu-shares"][0], "not expected cpu-shares option")
|
||||
}
|
||||
|
||||
func testGuestinfo(t *testing.T) {
|
||||
kv := map[string]string{
|
||||
"guestinfo.vice..init.sessions|docker-personality.detail.createtime": "0",
|
||||
"guestinfo.vice./init/sessions|docker-personality/Group": "",
|
||||
"init/networks|client/Common/notes": "",
|
||||
"guestinfo.vice./network/container_networks|vnet/gateway/IP": "AAAAAAAAAAAAAP//CgoKAQ==",
|
||||
"guestinfo.vice./init/sessions|port-layer/tty": "false",
|
||||
"guestinfo.vice..init.networks|bridge.network.type": "bridge",
|
||||
"vmotion.checkpointSVGAPrimarySize": "4194304",
|
||||
"guestinfo.vice./storage/image_stores|0/Host": "vsanDatastore",
|
||||
"guestinfo.vice./init/sessions|vicadmin/stopSignal": "",
|
||||
"guestinfo.vice./network/container_networks|vnet/Common/id": "DistributedVirtualPortgroup:dvportgroup-357",
|
||||
"guestinfo.vice..init.networks|client.network.assigned.dns|1": "CqYBAQ==",
|
||||
"guestinfo.vice..init.sessions|vicadmin.status": "0",
|
||||
"guestinfo.vice./init/networks|bridge/Common/id": "224",
|
||||
"guestinfo.vice./init/networks|bridge/network/Common/id": "DistributedVirtualPortgroup:dvportgroup-55",
|
||||
"guestinfo.vice./init/diagnostics/debug": "3",
|
||||
"guestinfo.vice./storage/VolumeLocations|default/ForceQuery": "false",
|
||||
"guestinfo.vice./init/sessions|docker-personality/active": "true",
|
||||
"guestinfo.vice./container/ComputeResources": "0",
|
||||
"guestinfo.vice..init.networks|client.network.assigned.gateway.Mask": "///gAA==",
|
||||
"guestinfo.vice./storage/VolumeLocations|vol1/Opaque": "",
|
||||
"guestinfo.vice..init.networks|management.assigned.IP": "CsCoOQ==",
|
||||
"guestinfo.vice./init/sessions|port-layer/common/ExecutionEnvironment": "",
|
||||
"guestinfo.vice./init/sessions|docker-personality/stopSignal": "",
|
||||
"init/networks|management/Common/notes": "",
|
||||
"guestinfo.vice./storage/VolumeLocations|vol1/RawPath": "",
|
||||
"network/container_networks|bridge/Common/notes": "",
|
||||
"guestinfo.vice..network.container_networks|vmnet.type": "external",
|
||||
"guestinfo.vice..init.sessions|docker-personality.detail.stoptime": "0",
|
||||
"guestinfo.vice./init/sessions|vicadmin/cmd/Dir": "/home/vicadmin",
|
||||
"guestinfo.vice./connect/keepalive": "0",
|
||||
"guestinfo.vice..init.networks|management.network.type": "",
|
||||
"guestinfo.vice./init/sessions|port-layer/common/name": "port-layer",
|
||||
"guestinfo.vice..init.networks|public.network.assigned.dns|1": "CqYBAQ==",
|
||||
"guestinfo.vice./init/sessions|docker-personality/common/ExecutionEnvironment": "",
|
||||
"guestinfo.vice./init/sessions|vicadmin/openstdin": "false",
|
||||
"guestinfo.vice./connect/target_thumbprint": "9F:F0:DF:BA:7F:E2:89:F0:98:E4:A6:D1:58:24:68:74:8A:9B:25:6F",
|
||||
"guestinfo.vice..init.networks|public.network.type": "",
|
||||
"guestinfo.vice./init/sessions|docker-personality/cmd/Path": "/sbin/docker-engine-server",
|
||||
"guestinfo.vice..init.createtime": "0",
|
||||
"guestinfo.vice./init/sessions|port-layer/active": "true",
|
||||
"guestinfo.vice./network/container_networks|vnet/pools|0/last": "CgoK/w==",
|
||||
"init/sessions|docker-personality/common/notes": "",
|
||||
"guestinfo.vice..init.diagnostics.resurrections": "0",
|
||||
"guestinfo.vice..init.networks|public.network.assigned.gateway.Mask": "///gAA==",
|
||||
"guestinfo.vice./init/sessions|port-layer/cmd/Dir": "",
|
||||
"guestinfo.vice./network/container_networks|vnet/pools": "0",
|
||||
"guestinfo.vice./init/sessions|vicadmin/tty": "false",
|
||||
"guestinfo.vice./storage/image_stores|0/RawPath": "",
|
||||
"guestinfo.vice./init/sessions|port-layer/attach": "false",
|
||||
"guestinfo.vice./container/ComputeResources|0/Type": "VirtualApp",
|
||||
"guestinfo.vice./storage/image_stores|0/Scheme": "ds",
|
||||
"guestinfo.vice..init.networks|public.assigned.Mask": "///gAA==",
|
||||
"guestinfo.vice./init/networks|bridge/Common/ExecutionEnvironment": "",
|
||||
"guestinfo.vice./storage/image_stores|0/Path": "91512359-242e-5a30-ba4a-0200360a2e79",
|
||||
"numa.autosize.vcpu.maxPerVirtualNode": "1",
|
||||
"guestinfo.vice./init/version/BuildDate": "2017/05/06@14:16:54",
|
||||
"guestinfo.vice./init/sessions|docker-personality/User": "",
|
||||
"guestinfo.vice..init.networks|client.assigned.Mask": "///gAA==",
|
||||
"guestinfo.vice./init/networks|management/network/Common/id": "DistributedVirtualPortgroup:dvportgroup-56",
|
||||
"guestinfo.vice./connect/target": "https://10.192.171.116",
|
||||
"guestinfo.vice./storage/image_stores|0/RawQuery": "",
|
||||
"guestinfo.vice./init/sessions|docker-personality/runblock": "false",
|
||||
"guestinfo.vice..init.networks|management.network.assigned.gateway.IP": "CsC//Q==",
|
||||
"init/networks|management/network/Common/notes": "",
|
||||
"guestinfo.vice./init/version/GitCommit": "65a1889",
|
||||
"guestinfo.vice./init/networks|public/Common/name": "public",
|
||||
"guestinfo.vice./init/version/Version": "v1.1.0-rc3",
|
||||
"guestinfo.vice./storage/VolumeLocations|vol1/Scheme": "ds",
|
||||
"answer.msg.serial.file.open": "Append",
|
||||
"guestinfo.vice./storage/VolumeLocations|default/Path": "[vsanDatastore] volumes/default",
|
||||
"guestinfo.vice..init.networks|management.network.assigned.dns": "1",
|
||||
"guestinfo.vice./init/networks|public/network/Common/id": "DistributedVirtualPortgroup:dvportgroup-56",
|
||||
"guestinfo.vice./storage/image_stores|0/ForceQuery": "false",
|
||||
"guestinfo.vice./init/imageid": "",
|
||||
"guestinfo.vice./init/sessions|docker-personality/cmd/Env": "3",
|
||||
"guestinfo.vice./init/sessions|docker-personality/common/id": "docker-personality",
|
||||
"guestinfo.vice./storage/VolumeLocations|vol1/Fragment": "",
|
||||
"guestinfo.vice./init/sessions|vicadmin/cmd/Env": "3",
|
||||
"guestinfo.vice./init/sessions|vicadmin/common/ExecutionEnvironment": "",
|
||||
"guestinfo.vice./init/sessions|vicadmin/User": "vicadmin",
|
||||
"guestinfo.vice..init.networks|public.network.assigned.dns": "1",
|
||||
"network/container_networks|vnet/Common/notes": "",
|
||||
"guestinfo.vice./init/sessions|vicadmin/common/id": "vicadmin",
|
||||
"guestinfo.vice..init.sessions|docker-personality.diagnostics.resurrections": "0",
|
||||
"guestinfo.vice./init/sessions|vicadmin/cmd/Args~": "/sbin/vicadmin|--dc=vcqaDC|--pool=|--cluster=/vcqaDC/host/cls",
|
||||
"guestinfo.vice./init/networks|public/static": "false",
|
||||
"guestinfo.vice./init/networks|client/network/default": "false",
|
||||
"init/networks|public/network/Common/notes": "",
|
||||
"guestinfo.vice./init/sessions|docker-personality/restart": "true",
|
||||
"guestinfo.vice./storage/VolumeLocations|vol1/Host": "vsanDatastore",
|
||||
"guestinfo.vice./init/networks|bridge/network/Common/name": "bridge",
|
||||
"guestinfo.vice..init.sessions|vicadmin.detail.stoptime": "0",
|
||||
"guestinfo.vice./container/ComputeResources|0/Value": "resgroup-v364",
|
||||
"guestinfo.vice./network/container_networks|vmnet/Common/name": "vmnet",
|
||||
"guestinfo.vice./network/container_networks": "bridge|vmnet|vnet",
|
||||
"guestinfo.vice./init/sessions|docker-personality/cmd/Args": "2",
|
||||
"guestinfo.vice./init/networks|bridge/ip/IP": "AAAAAAAAAAAAAP//AAAAAA==",
|
||||
"guestinfo.vice./init/networks|bridge/network/Common/ExecutionEnvironment": "",
|
||||
"guestinfo.vice./storage/VolumeLocations|default/RawQuery": "",
|
||||
"guestinfo.vice..init.networks|public.assigned.IP": "CsCoOQ==",
|
||||
"guestinfo.vice./init/networks|bridge/Common/name": "bridge",
|
||||
"guestinfo.vice./init/sessions|port-layer/openstdin": "false",
|
||||
"guestinfo.vice./create_bridge_network": "false",
|
||||
"guestinfo.vice./init/networks|management/Common/id": "192",
|
||||
"guestinfo.vice..init.networks|management.network.assigned.dns|0": "CqLMAQ==",
|
||||
"guestinfo.vice..init.networks|public.network.assigned.dns|0": "CqLMAQ==",
|
||||
"guestinfo.vice..init.sessions|docker-personality.started": "true",
|
||||
"init/networks|public/Common/notes": "",
|
||||
"guestinfo.vice..init.networks|management.network.assigned.dns|1": "CqYBAQ==",
|
||||
"guestinfo.vice./init/sessions|vicadmin/active": "true",
|
||||
"guestinfo.vice./init/networks": "bridge|client|management|public",
|
||||
"guestinfo.vice./init/networks|client/Common/id": "192",
|
||||
"guestinfo.vice./network/container_networks|vnet/Common/ExecutionEnvironment": "",
|
||||
"init/networks|client/network/Common/notes": "",
|
||||
"guestinfo.vice..init.sessions|port-layer.detail.stoptime": "0",
|
||||
"guestinfo.vice./init/sessions|docker-personality/cmd/Args~": "/sbin/docker-engine-server|-port=2376|-port-layer-port=2377",
|
||||
"guestinfo.vice..init.sessions|vicadmin.runblock": "false",
|
||||
"guestinfo.vice./init/sessions|port-layer/Group": "",
|
||||
"guestinfo.vice./init/version/PluginVersion": "6",
|
||||
"guestinfo.vice./storage/scratch_size": "8000000",
|
||||
"guestinfo.vice./init/networks|client/Common/ExecutionEnvironment": "",
|
||||
"init/networks|bridge/network/Common/notes": "",
|
||||
"guestinfo.vice./storage/VolumeLocations|default/Scheme": "ds",
|
||||
"guestinfo.vice..init.sessions|vicadmin.diagnostics.resurrections": "0",
|
||||
"guestinfo.vice./storage/VolumeLocations|vol1/ForceQuery": "false",
|
||||
"guestinfo.vice./network/container_networks|vmnet/default": "false",
|
||||
"guestinfo.vice..init.sessions|vicadmin.detail.createtime": "0",
|
||||
"guestinfo.vice./storage/VolumeLocations": "default|vol1",
|
||||
"guestinfo.vice./storage/VolumeLocations|vol1/Path": "[vsanDatastore] volumes/vol1",
|
||||
"guestinfo.vice./init/sessions|vicadmin/cmd/Args": "3",
|
||||
"guestinfo.vice..init.sessions|docker-personality.runblock": "false",
|
||||
"guestinfo.vice./init/version/BuildNumber": "10373",
|
||||
"guestinfo.vice./network/container_networks|vmnet/Common/id": "DistributedVirtualPortgroup:dvportgroup-358",
|
||||
"guestinfo.vice./init/networks|client/network/Common/id": "DistributedVirtualPortgroup:dvportgroup-56",
|
||||
"guestinfo.vice..init.sessions|docker-personality.status": "0",
|
||||
"guestinfo.vice..init.sessions|port-layer.started": "true",
|
||||
"guestinfo.vice./init/sessions|port-layer/runblock": "false",
|
||||
"guestinfo.vice./init/sessions|port-layer/diagnostics/debug": "0",
|
||||
"init/common/name": "test1",
|
||||
"guestinfo.vice..init.sessions|docker-personality.detail.starttime": "0",
|
||||
"guestinfo.vice./storage/VolumeLocations|vol1/RawQuery": "",
|
||||
"init/common/notes": "",
|
||||
"guestinfo.vice./network/container_networks|bridge/Common/ExecutionEnvironment": "",
|
||||
"guestinfo.vice..init.networks|client.network.assigned.gateway.IP": "CsC//Q==",
|
||||
"guestinfo.vice..init.sessions|vicadmin.detail.starttime": "0",
|
||||
"guestinfo.vice./init/networks|client/Common/name": "client",
|
||||
"guestinfo.vice./init/networks|bridge/static": "true",
|
||||
"network/container_networks|vmnet/Common/notes": "",
|
||||
"guestinfo.vice./init/repo": "",
|
||||
"guestinfo.vice./storage/image_stores": "0",
|
||||
"guestinfo.vice./init/sessions|vicadmin/runblock": "false",
|
||||
"guestinfo.vice./network/container_networks|vnet/gateway/Mask": "////AA==",
|
||||
"guestinfo.vice..init.networks|client.network.assigned.dns|0": "CqLMAQ==",
|
||||
"guestinfo.vice./init/networks|public/Common/id": "192",
|
||||
"guestinfo.vice./init/networks|public/Common/ExecutionEnvironment": "",
|
||||
"guestinfo.vice./init/networks|public/network/default": "true",
|
||||
"guestinfo.vice./storage/VolumeLocations|default/Fragment": "",
|
||||
"guestinfo.vice./init/asymrouting": "false",
|
||||
"guestinfo.vice..init.sessions|vicadmin.started": "true",
|
||||
"guestinfo.vice./init/sessions|port-layer/User": "",
|
||||
"guestinfo.vice./init/networks|management/Common/name": "",
|
||||
"guestinfo.vice./init/sessions|docker-personality/cmd/Dir": "",
|
||||
"guestinfo.vice./network/container_networks|vnet/Common/name": "vnet",
|
||||
"guestinfo.vice./network/container_networks|bridge/Common/id": "DistributedVirtualPortgroup:dvportgroup-55",
|
||||
"init/sessions|port-layer/common/notes": "",
|
||||
"guestinfo.vice./network/bridge-ip-range/IP": "rBAAAA==",
|
||||
"guestinfo.vice..init.networks|management.network.assigned.gateway.Mask": "///gAA==",
|
||||
"guestinfo.vice./init/sessions|vicadmin/common/name": "vicadmin",
|
||||
"guestinfo.vice./init/networks|management/network/Common/ExecutionEnvironment": "",
|
||||
"guestinfo.vice./init/networks|management/static": "false",
|
||||
"guestinfo.vice./init/common/id": "VirtualMachine:vm-365",
|
||||
"guestinfo.vice./network/bridge-ip-range/Mask": "//AAAA==",
|
||||
"guestinfo.vice./vic_machine_create_options": "13",
|
||||
"guestinfo.vice..init.sessions|port-layer.status": "0",
|
||||
"guestinfo.vice./storage/VolumeLocations|default/Opaque": "",
|
||||
"guestinfo.vice./init/networks|client/static": "false",
|
||||
"guestinfo.vice./init/networks|management/network/Common/name": "management",
|
||||
"guestinfo.vice./init/sessions|docker-personality/openstdin": "false",
|
||||
"guestinfo.vice./init/sessions|port-layer/cmd/Args": "2",
|
||||
"guestinfo.vice..init.networks|client.assigned.IP": "CsCoOQ==",
|
||||
"guestinfo.vice./init/networks|public/network/Common/ExecutionEnvironment": "",
|
||||
"guestinfo.vice./init/version/State": "",
|
||||
"guestinfo.vice./network/container_networks|vmnet/Common/ExecutionEnvironment": "",
|
||||
"guestinfo.vice./init/sessions|docker-personality/attach": "false",
|
||||
"guestinfo.vice./storage/image_stores|0/Opaque": "",
|
||||
"guestinfo.vice..init.networks|client.network.assigned.dns": "1",
|
||||
"guestinfo.vice./init/sessions|port-layer/cmd/Env~": "VC_URL=https://10.192.171.116|DC_PATH=vcqaDC|CS_PATH=/vcqaDC/host/cls|POOL_PATH=|DS_PATH=vsanDatastore",
|
||||
"guestinfo.vice./network/container_networks|bridge/Common/name": "bridge",
|
||||
"guestinfo.vice./storage/VolumeLocations|default/Host": "vsanDatastore",
|
||||
"guestinfo.vice./init/sessions|port-layer/cmd/Env": "4",
|
||||
"guestinfo.vice..init.sessions|port-layer.detail.starttime": "0",
|
||||
"guestinfo.vice./init/networks|management/Common/ExecutionEnvironment": "",
|
||||
"guestinfo.vice./init/sessions": "docker-personality|port-layer|vicadmin",
|
||||
"guestinfo.vice..init.sessions|port-layer.detail.createtime": "0",
|
||||
"guestinfo.vice./storage/image_stores|0/Fragment": "",
|
||||
"guestinfo.vice..init.networks|management.assigned.Mask": "///gAA==",
|
||||
"guestinfo.vice./network/container_networks|bridge/default": "false",
|
||||
"guestinfo.vice./container/ContainerNameConvention": "",
|
||||
"guestinfo.vice..init.networks|client.network.type": "",
|
||||
"guestinfo.vice./init/sessions|vicadmin/restart": "true",
|
||||
"guestinfo.vice./init/networks|public/network/Common/name": "public",
|
||||
"guestinfo.vice..init.sessions|port-layer.runblock": "false",
|
||||
"guestinfo.vice./init/sessions|port-layer/common/id": "port-layer",
|
||||
"guestinfo.vice./init/networks|client/network/Common/name": "client",
|
||||
"guestinfo.vice./network/container_networks|vnet/pools|0/first": "CgoKAA==",
|
||||
"guestinfo.vice..init.networks|public.network.assigned.gateway.IP": "CsC//Q==",
|
||||
"guestinfo.vice./init/layerid": "",
|
||||
"guestinfo.vice./init/sessions|docker-personality/common/name": "docker-personality",
|
||||
"guestinfo.vice./init/sessions|docker-personality/diagnostics/debug": "0",
|
||||
"guestinfo.vice./container/bootstrap_image_path": "[vsanDatastore] 91512359-242e-5a30-ba4a-0200360a2e79/V1.1.0-RC3-10373-65A1889-bootstrap.iso",
|
||||
"guestinfo.vice..network.container_networks|bridge.type": "bridge",
|
||||
"guestinfo.vice..network.container_networks|vnet.type": "external", "migrate.hostLogState": "none",
|
||||
"init/sessions|vicadmin/common/notes": "",
|
||||
"guestinfo.vice./init/sessions|vicadmin/diagnostics/debug": "0",
|
||||
"guestinfo.vice./init/sessions|vicadmin/attach": "false",
|
||||
"guestinfo.vice./init/sessions|vicadmin/Group": "vicadmin",
|
||||
"guestinfo.vice./storage/VolumeLocations|default/RawPath": "",
|
||||
"guestinfo.vice./init/networks|bridge/network/default": "false",
|
||||
"guestinfo.vice./init/sessions|port-layer/stopSignal": "",
|
||||
"guestinfo.vice./connect/username": "administrator@vsphere.local",
|
||||
"guestinfo.vice..init.sessions|port-layer.diagnostics.resurrections": "0",
|
||||
"guestinfo.vice./init/common/ExecutionEnvironment": "",
|
||||
"guestinfo.vice./network/bridge_network": "bridge",
|
||||
"guestinfo.vice./registry/whitelist_registries": "1",
|
||||
"guestinfo.vice./registry/whitelist_registries~": "harbor.com:2345|insecure:2345",
|
||||
"guestinfo.vice./registry/insecure_registries~": "insecure:2345",
|
||||
"guestinfo.vice./registry/insecure_registries": "0",
|
||||
"guestinfo.vice./init/sessions|vicadmin/cmd/Env~": "PATH=/sbin:/bin|GOTRACEBACK=all|HTTP_PROXY=http://proxy.vmware.com:2318|HTTPS_PROXY=https://proxy.vmware.com:2318",
|
||||
"guestinfo.vice./init/sessions|docker-personality/cmd/Env~": "PATH=/sbin|GOTRACEBACK=all|HTTP_PROXY=http://proxy.vmware.com:2318|HTTPS_PROXY=https://proxy.vmware.com:2318",
|
||||
}
|
||||
|
||||
commands := map[string][]string{
|
||||
"target": {"https://10.192.171.116"},
|
||||
"thumbprint": {"9F:F0:DF:BA:7F:E2:89:F0:98:E4:A6:D1:58:24:68:74:8A:9B:25:6F"},
|
||||
"image-store": {"vsanDatastore"},
|
||||
"volume-store": {
|
||||
"vsanDatastore/volumes/default:default",
|
||||
"vsanDatastore/volumes/vol1:vol1",
|
||||
},
|
||||
"bridge-network": {"bridge"},
|
||||
"public-network": {"external"},
|
||||
"container-network": {"management:vnet",
|
||||
"vm-network:vmnet",
|
||||
},
|
||||
"container-network-gateway": {"management:10.10.10.1/24"},
|
||||
"container-network-ip-range": {"management:10.10.10.0/24"},
|
||||
|
||||
"debug": {"3"},
|
||||
"insecure-registry": {"insecure:2345"},
|
||||
"whitelist-registry": {"harbor.com:2345", "insecure:2345"},
|
||||
"http-proxy": {"http://proxy.vmware.com:2318"},
|
||||
"https-proxy": {"https://proxy.vmware.com:2318"},
|
||||
}
|
||||
conf := &config.VirtualContainerHostConfigSpec{}
|
||||
extraconfig.DecodeWithPrefix(extraconfig.MapSource(kv), conf, "")
|
||||
|
||||
ctx := context.Background()
|
||||
d, err := validate.NewDataFromConfig(ctx, &testFinder{}, conf)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
dest, err := DataToOption(d)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
for k, v := range dest {
|
||||
if strings.Contains(k, "store") {
|
||||
for i := range v {
|
||||
v[i] = strings.TrimLeft(v[i], "ds://")
|
||||
}
|
||||
}
|
||||
c := commands[k]
|
||||
testArrays(t, c, v, k)
|
||||
}
|
||||
for k, v := range commands {
|
||||
if strings.Contains(k, "store") {
|
||||
for i := range v {
|
||||
v[i] = strings.TrimLeft(v[i], "ds://")
|
||||
}
|
||||
}
|
||||
c := dest[k]
|
||||
testArrays(t, v, c, k)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testArrays(t *testing.T, l, r []string, key string) {
|
||||
for i := range l {
|
||||
found := false
|
||||
for j := range r {
|
||||
if l[i] == r[j] {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, fmt.Sprintf("option value %s mismatch: value %s in array %s is not found in array %s", key, l[i], l, r))
|
||||
}
|
||||
}
|
||||
0
vendor/github.com/vmware/vic/cmd/vic-machine/create/cert.pfx
generated
vendored
Normal file
0
vendor/github.com/vmware/vic/cmd/vic-machine/create/cert.pfx
generated
vendored
Normal file
789
vendor/github.com/vmware/vic/cmd/vic-machine/create/create.go
generated
vendored
Normal file
789
vendor/github.com/vmware/vic/cmd/vic-machine/create/create.go
generated
vendored
Normal file
@@ -0,0 +1,789 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/lib/install/management"
|
||||
"github.com/vmware/vic/lib/install/validate"
|
||||
"github.com/vmware/vic/lib/install/vchlog"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
// Max permitted length of Virtual Machine name
|
||||
MaxVirtualMachineNameLen = 80
|
||||
// Max permitted length of Virtual Switch name
|
||||
MaxDisplayNameLen = 31
|
||||
)
|
||||
|
||||
var EntireOptionHelpTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}}
|
||||
|
||||
CATEGORY:
|
||||
{{.Category}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{.Description}}{{end}}{{if .VisibleFlags}}
|
||||
|
||||
OPTIONS:
|
||||
{{range .Flags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
// Create has all input parameters for vic-machine create command
|
||||
type Create struct {
|
||||
common.Networks
|
||||
*data.Data
|
||||
Certs common.CertFactory
|
||||
containerNetworks common.CNetworks
|
||||
Registries common.Registries
|
||||
|
||||
volumeStores common.VolumeStores
|
||||
|
||||
Nameservers common.DNS
|
||||
|
||||
memoryReservLimits string
|
||||
cpuReservLimits string
|
||||
|
||||
advancedOptions bool
|
||||
BridgeIPRange string
|
||||
|
||||
Proxies common.Proxies
|
||||
|
||||
SyslogAddr string
|
||||
|
||||
executor *management.Dispatcher
|
||||
}
|
||||
|
||||
func NewCreate() *Create {
|
||||
create := &Create{}
|
||||
create.Data = data.NewData()
|
||||
|
||||
return create
|
||||
}
|
||||
|
||||
// SetFields iterates through the fields in the Create struct, searching for fields
|
||||
// tagged with the `arg` key. If the value of that tag matches the supplied `flag`
|
||||
// string, a nil check is performed. If the field is not nil, then the user supplied
|
||||
// this flag on the command line and we need to persist it.
|
||||
// This is a workaround for cli.Context.IsSet() returning false when
|
||||
// the short option for a cli.StringSlice is supplied instead of the long option.
|
||||
// See https://github.com/urfave/cli/issues/314
|
||||
func (c *Create) SetFields() map[string]struct{} {
|
||||
result := make(map[string]struct{})
|
||||
create := reflect.ValueOf(c).Elem()
|
||||
for i := 0; i < create.NumField(); i++ {
|
||||
t := create.Type().Field(i)
|
||||
if tag := t.Tag.Get("arg"); tag != "" {
|
||||
ss := create.Field(i)
|
||||
if !ss.IsNil() {
|
||||
result[tag] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Flags return all cli flags for create
|
||||
func (c *Create) Flags() []cli.Flag {
|
||||
create := []cli.Flag{
|
||||
// images
|
||||
cli.StringFlag{
|
||||
Name: "image-store, i",
|
||||
Value: "",
|
||||
Usage: "Image datastore path in format \"datastore/path\"",
|
||||
Destination: &c.ImageDatastorePath,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "base-image-size",
|
||||
Value: constants.DefaultBaseImageScratchSize,
|
||||
Usage: "Specify the size of the base image from which all other images are created e.g. 8GB/8000MB",
|
||||
Destination: &c.ScratchSize,
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
|
||||
networks := []cli.Flag{
|
||||
// bridge
|
||||
cli.StringFlag{
|
||||
Name: "bridge-network, b",
|
||||
Value: "",
|
||||
Usage: "The bridge network port group name (private port group for containers). Defaults to the Virtual Container Host name",
|
||||
Destination: &c.BridgeNetworkName,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "bridge-network-range, bnr",
|
||||
Value: "172.16.0.0/12",
|
||||
Usage: "The IP range from which bridge networks are allocated",
|
||||
Destination: &c.BridgeIPRange,
|
||||
Hidden: true,
|
||||
},
|
||||
|
||||
// client
|
||||
cli.StringFlag{
|
||||
Name: "client-network, cln",
|
||||
Value: "",
|
||||
Usage: "The client network port group name (restricts DOCKER_API access to this network). Defaults to DCHP - see advanced help (-x)",
|
||||
Destination: &c.ClientNetworkName,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "client-network-gateway",
|
||||
Value: "",
|
||||
Usage: "Gateway for the VCH on the client network, including one or more routing destinations in a comma separated list, e.g. 10.1.0.0/16,10.2.0.0/16:10.0.0.1",
|
||||
Destination: &c.ClientNetworkGateway,
|
||||
Hidden: true,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "client-network-ip",
|
||||
Value: "",
|
||||
Usage: "IP address with a network mask for the VCH on the client network, e.g. 10.0.0.2/24",
|
||||
Destination: &c.ClientNetworkIP,
|
||||
Hidden: true,
|
||||
},
|
||||
|
||||
// public
|
||||
cli.StringFlag{
|
||||
Name: "public-network, pn",
|
||||
Value: "VM Network",
|
||||
Usage: "The public network port group name (port forwarding and default route). Defaults to 'VM Network' and DHCP -- see advanced help (-x)",
|
||||
Destination: &c.PublicNetworkName,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "public-network-gateway",
|
||||
Value: "",
|
||||
Usage: "Gateway for the VCH on the public network, e.g. 10.0.0.1",
|
||||
Destination: &c.PublicNetworkGateway,
|
||||
Hidden: true,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "public-network-ip",
|
||||
Value: "",
|
||||
Usage: "IP address with a network mask for the VCH on the public network, e.g. 10.0.1.2/24",
|
||||
Destination: &c.PublicNetworkIP,
|
||||
Hidden: true,
|
||||
},
|
||||
|
||||
// management
|
||||
cli.StringFlag{
|
||||
Name: "management-network, mn",
|
||||
Value: "",
|
||||
Usage: "The management network port group name (provides route to target hosting vSphere). Defaults to DCHP - see advanced help (-x)",
|
||||
Destination: &c.ManagementNetworkName,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "management-network-gateway",
|
||||
Value: "",
|
||||
Usage: "Gateway for the VCH on the management network, including one or more routing destinations in a comma separated list, e.g. 10.1.0.0/16,10.2.0.0/16:10.0.0.1",
|
||||
Destination: &c.ManagementNetworkGateway,
|
||||
Hidden: true,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "management-network-ip",
|
||||
Value: "",
|
||||
Usage: "IP address with a network mask for the VCH on the management network, e.g. 10.0.2.2/24",
|
||||
Destination: &c.ManagementNetworkIP,
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
var memory, cpu []cli.Flag
|
||||
memory = append(memory, c.VCHMemoryLimitFlags(true)...)
|
||||
memory = append(memory,
|
||||
cli.IntFlag{
|
||||
Name: "endpoint-memory",
|
||||
Value: constants.DefaultEndpointMemoryMB,
|
||||
Usage: "Memory for the VCH endpoint VM, in MB. Does not impact resources allocated per container.",
|
||||
Hidden: true,
|
||||
Destination: &c.MemoryMB,
|
||||
})
|
||||
cpu = append(cpu, c.VCHCPULimitFlags(true)...)
|
||||
cpu = append(cpu,
|
||||
cli.IntFlag{
|
||||
Name: "endpoint-cpu",
|
||||
Value: 1,
|
||||
Usage: "vCPUs for the VCH endpoint VM. Does not impact resources allocated per container.",
|
||||
Hidden: true,
|
||||
Destination: &c.NumCPUs,
|
||||
})
|
||||
|
||||
tls := c.Certs.CertFlags()
|
||||
|
||||
tls = append(tls, cli.BoolFlag{
|
||||
Name: "no-tls, k",
|
||||
Usage: "Disable TLS support completely",
|
||||
Destination: &c.Certs.NoTLS,
|
||||
Hidden: true,
|
||||
})
|
||||
|
||||
registries := c.Registries.Flags()
|
||||
registries = append(registries,
|
||||
cli.StringSliceFlag{
|
||||
Name: "insecure-registry, dir",
|
||||
Value: &c.Registries.InsecureRegistriesArg,
|
||||
Usage: "Specify a list of permitted insecure registry server addresses",
|
||||
})
|
||||
registries = append(registries,
|
||||
cli.StringSliceFlag{
|
||||
Name: "whitelist-registry, wr",
|
||||
Value: &c.Registries.WhitelistRegistriesArg,
|
||||
Usage: "Specify a list of permitted whitelist registry server addresses (insecure addresses still require the --insecure-registry option in addition)",
|
||||
})
|
||||
|
||||
syslog := []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "syslog-address",
|
||||
Value: "",
|
||||
Usage: "Address of the syslog server to send Virtual Container Host logs to. Must be in the format transport://host[:port], where transport is udp or tcp. port defaults to 514 if not specified",
|
||||
Destination: &c.SyslogAddr,
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
|
||||
util := []cli.Flag{
|
||||
// miscellaneous
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "Ignore error messages and proceed",
|
||||
Destination: &c.Force,
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Value: 3 * time.Minute,
|
||||
Usage: "Time to wait for create",
|
||||
Destination: &c.Timeout,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "asymmetric-routes, ar",
|
||||
Usage: "Set up the Virtual Container Host for asymmetric routing",
|
||||
Destination: &c.AsymmetricRouting,
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
|
||||
help := []cli.Flag{
|
||||
// help options
|
||||
cli.BoolFlag{
|
||||
Name: "extended-help, x",
|
||||
Usage: "Show all options - this must be specified instead of --help",
|
||||
Destination: &c.advancedOptions,
|
||||
},
|
||||
}
|
||||
|
||||
target := c.TargetFlags()
|
||||
ops := c.OpsCredentials.Flags(true)
|
||||
compute := c.ComputeFlags()
|
||||
container := c.ContainerFlags()
|
||||
volume := c.volumeStores.Flags()
|
||||
iso := c.ImageFlags(true)
|
||||
cNetwork := c.containerNetworks.CNetworkFlags(true)
|
||||
dns := c.Nameservers.DNSFlags(true)
|
||||
proxies := c.Proxies.ProxyFlags(true)
|
||||
kubelet := c.Kubelet.Flags(true)
|
||||
debug := c.DebugFlags(true)
|
||||
|
||||
// flag arrays are declared, now combined
|
||||
var flags []cli.Flag
|
||||
for _, f := range [][]cli.Flag{target, compute, ops, create, container, volume, dns, networks, cNetwork, memory, cpu, tls, registries, proxies, syslog, iso, util, kubelet, debug, help} {
|
||||
flags = append(flags, f...)
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func (c *Create) ProcessParams(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if err := c.HasCredentials(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// prevent usage of special characters for certain user provided values
|
||||
if err := common.CheckUnsupportedChars(c.DisplayName); err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("--name contains unsupported characters: %s Allowed characters are alphanumeric, space and symbols - _ ( )", err), 1)
|
||||
}
|
||||
|
||||
if len(c.DisplayName) > MaxDisplayNameLen {
|
||||
return cli.NewExitError(fmt.Sprintf("Display name %s exceeds the permitted 31 characters limit. Please use a shorter -name parameter", c.DisplayName), 1)
|
||||
}
|
||||
|
||||
if c.BridgeNetworkName == "" {
|
||||
c.BridgeNetworkName = c.DisplayName
|
||||
}
|
||||
|
||||
// Pass admin credentials for use as ops credentials if ops credentials are not supplied.
|
||||
if err := c.OpsCredentials.ProcessOpsCredentials(op, true, c.Target.User, c.Target.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.Kubelet.ProcessKubelet(op, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
c.ContainerNetworks, err = c.containerNetworks.ProcessContainerNetworks(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.ProcessBridgeNetwork(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.ProcessNetwork(op, &c.Data.ClientNetwork, "client", c.ClientNetworkName,
|
||||
c.ClientNetworkIP, c.ClientNetworkGateway); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.ProcessNetwork(op, &c.Data.PublicNetwork, "public", c.PublicNetworkName,
|
||||
c.PublicNetworkIP, c.PublicNetworkGateway); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.ProcessNetwork(op, &c.Data.ManagementNetwork, "management", c.ManagementNetworkName,
|
||||
c.ManagementNetworkIP, c.ManagementNetworkGateway); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.DNS, err = c.Nameservers.ProcessDNSServers(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// must come after client network processing as it checks for static IP on that interface
|
||||
if err = c.processCertificates(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = common.CheckUnsupportedCharsDatastore(c.ImageDatastorePath); err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("--image-store contains unsupported characters: %s Allowed characters are alphanumeric, space and symbols - _ ( ) / :", err), 1)
|
||||
}
|
||||
|
||||
c.VolumeLocations, err = c.volumeStores.ProcessVolumeStores()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.Registries.ProcessRegistries(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.InsecureRegistries = c.Registries.InsecureRegistries
|
||||
c.WhitelistRegistries = c.Registries.WhitelistRegistries
|
||||
c.RegistryCAs = c.Registries.RegistryCAs
|
||||
|
||||
hproxy, sproxy, err := c.Proxies.ProcessProxies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.HTTPProxy = hproxy
|
||||
c.HTTPSProxy = sproxy
|
||||
|
||||
if err = c.ProcessSyslog(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Create) processCertificates(op trace.Operation) error {
|
||||
|
||||
// debuglevel is a pointer now so we have to do this song and dance
|
||||
var debug int
|
||||
if c.Debug.Debug == nil {
|
||||
debug = 0
|
||||
} else {
|
||||
debug = *c.Debug.Debug
|
||||
}
|
||||
|
||||
c.Certs.Networks = c.Networks
|
||||
|
||||
if err := c.Certs.ProcessCertificates(op, c.DisplayName, c.Force, debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// copy a few things out of seed because ProcessCertificates has side effects
|
||||
c.KeyPEM = c.Certs.KeyPEM
|
||||
c.CertPEM = c.Certs.CertPEM
|
||||
c.ClientCAs = c.Certs.ClientCAs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Create) ProcessBridgeNetwork() error {
|
||||
// bridge network params
|
||||
var err error
|
||||
|
||||
_, c.Data.BridgeIPRange, err = net.ParseCIDR(c.BridgeIPRange)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("Error parsing bridge network ip range: %s. Range must be in CIDR format, e.g., 172.16.0.0/12", err), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseGatewaySpec(gw string) (cidrs []net.IPNet, gwIP net.IPNet, err error) {
|
||||
ss := strings.Split(gw, ":")
|
||||
if len(ss) > 2 {
|
||||
err = fmt.Errorf("gateway %s specified incorrectly", gw)
|
||||
return
|
||||
}
|
||||
|
||||
gwStr := ss[0]
|
||||
cidrsStr := ""
|
||||
if len(ss) > 1 {
|
||||
gwStr = ss[1]
|
||||
cidrsStr = ss[0]
|
||||
}
|
||||
|
||||
if gwIP.IP = net.ParseIP(gwStr); gwIP.IP == nil {
|
||||
err = fmt.Errorf("Provided gateway IP address is not valid: %s", gwStr)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cidrsStr != "" {
|
||||
for _, c := range strings.Split(cidrsStr, ",") {
|
||||
var ipnet *net.IPNet
|
||||
_, ipnet, err = net.ParseCIDR(c)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid CIDR in gateway specification: %s", err)
|
||||
return
|
||||
}
|
||||
cidrs = append(cidrs, *ipnet)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ProcessNetwork parses network args if present
|
||||
func (c *Create) ProcessNetwork(op trace.Operation, network *data.NetworkConfig, netName, pgName, staticIP, gateway string) error {
|
||||
var err error
|
||||
|
||||
network.Name = pgName
|
||||
|
||||
if gateway != "" && staticIP == "" {
|
||||
return fmt.Errorf("Gateway provided without static IP for %s network", netName)
|
||||
}
|
||||
|
||||
defer func(net *data.NetworkConfig) {
|
||||
if err == nil {
|
||||
op.Debugf("%s network: IP %s gateway %s dest: %s", netName, net.IP, net.Gateway.IP, net.Destinations)
|
||||
}
|
||||
}(network)
|
||||
|
||||
var ipNet *net.IPNet
|
||||
if staticIP != "" {
|
||||
var ipAddr net.IP
|
||||
ipAddr, ipNet, err = net.ParseCIDR(staticIP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse the provided %s network IP address %s: %s", netName, staticIP, err)
|
||||
}
|
||||
|
||||
network.IP.IP = ipAddr
|
||||
network.IP.Mask = ipNet.Mask
|
||||
}
|
||||
|
||||
if gateway != "" {
|
||||
network.Destinations, network.Gateway, err = parseGatewaySpec(gateway)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid %s network gateway: %s", netName, err)
|
||||
}
|
||||
|
||||
if !network.IP.Contains(network.Gateway.IP) {
|
||||
return fmt.Errorf("%s gateway with IP %s is not reachable from %s", netName, network.Gateway.IP, ipNet.String())
|
||||
}
|
||||
|
||||
// TODO(vburenin): this seems ugly, and it actually is. The reason is that a gateway required to specify
|
||||
// a network mask for it, which is just not how network configuration should be done. Network mask has to
|
||||
// be provided separately or with the IP address. It is hard to change all dependencies to keep mask
|
||||
// with IP address, so it will be stored with network gateway as it was previously.
|
||||
network.Gateway.Mask = network.IP.Mask
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Create) ProcessSyslog() error {
|
||||
if len(c.SyslogAddr) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
u, err := url.Parse(c.SyslogAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.SyslogConfig.Addr = u
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Create) logArguments(op trace.Operation, cliContext *cli.Context) []string {
|
||||
args := []string{}
|
||||
sf := c.SetFields() // StringSlice options set by the user
|
||||
for _, f := range cliContext.FlagNames() {
|
||||
_, ok := sf[f]
|
||||
if !cliContext.IsSet(f) && !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// avoid logging sensitive data
|
||||
if f == "user" || f == "password" || f == "ops-password" {
|
||||
op.Debugf("--%s=<censored>", f)
|
||||
continue
|
||||
}
|
||||
|
||||
if f == "tls-server-cert" || f == "tls-cert-path" || f == "tls-server-key" || f == "registry-ca" || f == "tls-ca" {
|
||||
continue
|
||||
}
|
||||
|
||||
if f == "target" {
|
||||
url, err := url.Parse(cliContext.String(f))
|
||||
if err != nil {
|
||||
op.Debugf("Unable to re-parse target url for logging")
|
||||
continue
|
||||
}
|
||||
url.User = nil
|
||||
flag := fmt.Sprintf("--target=%s", url.String())
|
||||
op.Debug(flag)
|
||||
args = append(args, flag)
|
||||
continue
|
||||
}
|
||||
|
||||
i := cliContext.Int(f)
|
||||
if i != 0 {
|
||||
flag := fmt.Sprintf("--%s=%d", f, i)
|
||||
op.Debug(flag)
|
||||
args = append(args, flag)
|
||||
continue
|
||||
}
|
||||
d := cliContext.Duration(f)
|
||||
if d != 0 {
|
||||
flag := fmt.Sprintf("--%s=%s", f, d.String())
|
||||
op.Debug(flag)
|
||||
args = append(args, flag)
|
||||
continue
|
||||
}
|
||||
x := cliContext.Float64(f)
|
||||
if x != 0 {
|
||||
flag := fmt.Sprintf("--%s=%f", f, x)
|
||||
op.Debug(flag)
|
||||
args = append(args, flag)
|
||||
continue
|
||||
}
|
||||
|
||||
// check for StringSlice before String as the cli String checker
|
||||
// will mistake a StringSlice for a String and jackaroo the formatting
|
||||
match := func() (result bool) {
|
||||
result = false
|
||||
defer func() { recover() }()
|
||||
ss := cliContext.StringSlice(f)
|
||||
if ss != nil {
|
||||
for _, o := range ss {
|
||||
flag := fmt.Sprintf("--%s=%s", f, o)
|
||||
op.Debug(flag)
|
||||
args = append(args, flag)
|
||||
}
|
||||
}
|
||||
return ss != nil
|
||||
}()
|
||||
if match {
|
||||
continue
|
||||
}
|
||||
|
||||
s := cliContext.String(f)
|
||||
if s != "" {
|
||||
flag := fmt.Sprintf("--%s=%s", f, s)
|
||||
op.Debug(flag)
|
||||
args = append(args, flag)
|
||||
continue
|
||||
}
|
||||
|
||||
b := cliContext.Bool(f)
|
||||
bT := cliContext.BoolT(f)
|
||||
if b && !bT {
|
||||
flag := fmt.Sprintf("--%s=%t", f, true)
|
||||
op.Debug(flag)
|
||||
args = append(args, flag)
|
||||
continue
|
||||
}
|
||||
|
||||
match = func() (result bool) {
|
||||
result = false
|
||||
defer func() { recover() }()
|
||||
is := cliContext.IntSlice(f)
|
||||
if is != nil {
|
||||
flag := fmt.Sprintf("--%s=%#v", f, is)
|
||||
op.Debug(flag)
|
||||
args = append(args, flag)
|
||||
}
|
||||
return is != nil
|
||||
}()
|
||||
if match {
|
||||
continue
|
||||
}
|
||||
|
||||
// generic last because it matches everything
|
||||
g := cliContext.Generic(f)
|
||||
if g != nil {
|
||||
flag := fmt.Sprintf("--%s=%#v", f, g)
|
||||
op.Debug(flag)
|
||||
args = append(args, flag)
|
||||
}
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func (c *Create) Run(clic *cli.Context) (err error) {
|
||||
|
||||
if c.advancedOptions {
|
||||
cli.HelpPrinter(clic.App.Writer, EntireOptionHelpTemplate, clic.Command)
|
||||
return nil
|
||||
}
|
||||
|
||||
// create the logger for streaming VCH log messages
|
||||
datastoreLog := vchlog.New()
|
||||
defer func(old io.Writer) {
|
||||
trace.Logger.Out = old
|
||||
datastoreLog.Close()
|
||||
}(trace.Logger.Out)
|
||||
trace.Logger.Out = io.MultiWriter(trace.Logger.Out, datastoreLog.GetPipe())
|
||||
go datastoreLog.Run()
|
||||
|
||||
// These operations will be executed without timeout
|
||||
op := common.NewOperation(clic, c.Debug.Debug)
|
||||
op.Infof("### Installing VCH ####")
|
||||
|
||||
defer func() {
|
||||
// urfave/cli will print out exit in error handling, so no more information in main method can be printed out.
|
||||
err = common.LogErrorIfAny(op, clic, err)
|
||||
}()
|
||||
|
||||
if err = c.ProcessParams(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := c.logArguments(op, clic)
|
||||
|
||||
var images map[string]string
|
||||
if images, err = c.CheckImagesFiles(op, c.Force); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(clic.Args()) > 0 {
|
||||
op.Errorf("Unknown argument: %s", clic.Args()[0])
|
||||
return errors.New("invalid CLI arguments")
|
||||
}
|
||||
|
||||
validator, err := validate.NewValidator(op, c.Data)
|
||||
if err != nil {
|
||||
op.Error("Create cannot continue: failed to create validator")
|
||||
return err
|
||||
}
|
||||
defer validator.Session.Logout(op)
|
||||
|
||||
vchConfig, err := validator.Validate(op, c.Data)
|
||||
if err != nil {
|
||||
op.Error("Create cannot continue: configuration validation failed")
|
||||
return err
|
||||
}
|
||||
|
||||
// persist cli args used to create the VCH
|
||||
vchConfig.VicMachineCreateOptions = args
|
||||
|
||||
vConfig := validator.AddDeprecatedFields(op, vchConfig, c.Data)
|
||||
vConfig.ImageFiles = images
|
||||
vConfig.ApplianceISO = path.Base(c.ApplianceISO)
|
||||
vConfig.BootstrapISO = path.Base(c.BootstrapISO)
|
||||
|
||||
vConfig.HTTPProxy = c.HTTPProxy
|
||||
vConfig.HTTPSProxy = c.HTTPSProxy
|
||||
|
||||
vConfig.Timeout = c.Data.Timeout
|
||||
|
||||
// separate initial validation from dispatch of creation task
|
||||
op.Info("")
|
||||
|
||||
executor := management.NewDispatcher(op, validator.Session, vchConfig, c.Force)
|
||||
if err = executor.CreateVCH(vchConfig, vConfig, datastoreLog); err != nil {
|
||||
executor.CollectDiagnosticLogs()
|
||||
op.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Perform the remaining work using a context with a timeout to ensure the user does not wait forever
|
||||
op, cancel := trace.WithTimeout(&op, c.Timeout, "Create")
|
||||
defer cancel()
|
||||
defer func() {
|
||||
if op.Err() == context.DeadlineExceeded {
|
||||
//context deadline exceeded, replace returned error message
|
||||
err = errors.Errorf("Creating VCH exceeded time limit of %s. Please increase the timeout using --timeout to accommodate for a busy vSphere target", c.Timeout)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = executor.CheckServiceReady(op, vchConfig, c.Certs.ClientCert); err != nil {
|
||||
executor.CollectDiagnosticLogs()
|
||||
cmd, _ := executor.GetDockerAPICommand(vchConfig, c.Certs.Ckey, c.Certs.Ccert, c.Certs.Cacert, c.Certs.CertPath)
|
||||
op.Info("\tAPI may be slow to start - try to connect to API after a few minutes:")
|
||||
if cmd != "" {
|
||||
op.Infof("\t\tRun command: %s", cmd)
|
||||
} else {
|
||||
op.Infof("\t\tRun %s inspect to find API connection command and run the command if ip address is ready", clic.App.Name)
|
||||
}
|
||||
op.Info("\t\tIf command succeeds, VCH is started. If command fails, VCH failed to install - see documentation for troubleshooting.")
|
||||
return err
|
||||
}
|
||||
|
||||
op.Info("Initialization of appliance successful")
|
||||
|
||||
// We must check for the volume stores that are present after the portlayer presents.
|
||||
|
||||
executor.ShowVCH(vchConfig, c.Certs.Ckey, c.Certs.Ccert, c.Certs.Cacert, c.Certs.EnvFile, c.Certs.CertPath)
|
||||
op.Info("Installer completed successfully")
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-time.After(3 * time.Second):
|
||||
op.Infof("Waiting for log upload to complete") // tell the user if the wait causes noticeable delay
|
||||
case <-op.Done():
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// wait on the logger to finish streaming
|
||||
datastoreLog.Close()
|
||||
datastoreLog.Wait(op)
|
||||
|
||||
return nil
|
||||
}
|
||||
95
vendor/github.com/vmware/vic/cmd/vic-machine/create/create_test.go
generated
vendored
Normal file
95
vendor/github.com/vmware/vic/cmd/vic-machine/create/create_test.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2016-2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package create
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseGatewaySpec(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in string
|
||||
dest []string
|
||||
gw string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
in: "10.10.10.10",
|
||||
gw: "10.10.10.10",
|
||||
},
|
||||
{
|
||||
in: "10.12.0.0/16:10.10.10.10",
|
||||
dest: []string{"10.12.0.0/16"},
|
||||
gw: "10.10.10.10",
|
||||
},
|
||||
{
|
||||
in: "10.13.0.0/16,10.12.0.0/16:10.10.10.10",
|
||||
dest: []string{"10.13.0.0/16", "10.12.0.0/16"},
|
||||
gw: "10.10.10.10",
|
||||
},
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
dest, gw, err := parseGatewaySpec(te.in)
|
||||
if te.err != nil {
|
||||
assert.EqualError(t, err, te.err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.NotNil(t, gw)
|
||||
assert.Equal(t, te.gw, gw.IP.String())
|
||||
|
||||
assert.Equal(t, len(te.dest), len(dest))
|
||||
for _, d := range te.dest {
|
||||
found := false
|
||||
for _, d2 := range dest {
|
||||
if d2.String() == d {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlags(t *testing.T) {
|
||||
c := NewCreate()
|
||||
flags := c.Flags()
|
||||
numberOfFlags := 62
|
||||
assert.Equal(t, numberOfFlags, len(flags), "Missing flags during Create.")
|
||||
}
|
||||
|
||||
func TestProcessBridgeNetwork(t *testing.T) {
|
||||
c := NewCreate()
|
||||
|
||||
c.BridgeIPRange = "172.16.0.0.0"
|
||||
improperBridgeIPRange := c.ProcessBridgeNetwork()
|
||||
assert.NotNil(t, improperBridgeIPRange)
|
||||
|
||||
c.BridgeIPRange = "172.16.0.0/12"
|
||||
properBridgeIPRange := c.ProcessBridgeNetwork()
|
||||
assert.Nil(t, properBridgeIPRange, "Bridge Network IP Range can't be parsed. Range must be in CIDR format, e.g., 172.16.0.0/12.")
|
||||
}
|
||||
|
||||
func TestSetFields(t *testing.T) {
|
||||
c := NewCreate()
|
||||
option := c.SetFields()
|
||||
assert.NotNil(t, option)
|
||||
}
|
||||
205
vendor/github.com/vmware/vic/cmd/vic-machine/debug/debug.go
generated
vendored
Normal file
205
vendor/github.com/vmware/vic/cmd/vic-machine/debug/debug.go
generated
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package debug
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/lib/install/management"
|
||||
"github.com/vmware/vic/lib/install/validate"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// Debug has all input parameters for vic-machine Debug command
|
||||
type Debug struct {
|
||||
*data.Data
|
||||
|
||||
executor *management.Dispatcher
|
||||
|
||||
enableSSH bool
|
||||
password string
|
||||
authorizedKey string
|
||||
}
|
||||
|
||||
func NewDebug() *Debug {
|
||||
d := &Debug{}
|
||||
d.Data = data.NewData()
|
||||
return d
|
||||
}
|
||||
|
||||
// Flags return all cli flags for Debug
|
||||
func (d *Debug) Flags() []cli.Flag {
|
||||
preFlags := append(d.TargetFlags(), d.IDFlags()...)
|
||||
preFlags = append(preFlags, d.ComputeFlags()...)
|
||||
|
||||
ssh := []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "enable-ssh, ssh",
|
||||
Usage: "Enable SSH server within appliance VM",
|
||||
Destination: &d.enableSSH,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "authorized-key, key",
|
||||
Value: "",
|
||||
Usage: "File with public key to place as /root/.ssh/authorized_keys",
|
||||
Destination: &d.authorizedKey,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "rootpw, pw",
|
||||
Value: "",
|
||||
Usage: "Password to set for root user (non-persistent over reboots)",
|
||||
Destination: &d.password,
|
||||
},
|
||||
}
|
||||
|
||||
util := []cli.Flag{
|
||||
cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Value: 3 * time.Minute,
|
||||
Usage: "Time to wait for operation to complete",
|
||||
Destination: &d.Timeout,
|
||||
},
|
||||
}
|
||||
|
||||
target := d.TargetFlags()
|
||||
id := d.IDFlags()
|
||||
compute := d.ComputeFlags()
|
||||
debug := d.DebugFlags(true)
|
||||
|
||||
// flag arrays are declared, now combined
|
||||
var flags []cli.Flag
|
||||
for _, f := range [][]cli.Flag{target, id, compute, ssh, util, debug} {
|
||||
flags = append(flags, f...)
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func (d *Debug) processParams(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if err := d.HasCredentials(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Debug) Run(clic *cli.Context) (err error) {
|
||||
op := common.NewOperation(clic, d.Debug.Debug)
|
||||
defer func() {
|
||||
// urfave/cli will print out exit in error handling, so no more information in main method can be printed out.
|
||||
err = common.LogErrorIfAny(op, clic, err)
|
||||
}()
|
||||
op, cancel := trace.WithTimeout(&op, d.Timeout, clic.App.Name)
|
||||
defer cancel()
|
||||
defer func() {
|
||||
if op.Err() != nil && op.Err() == context.DeadlineExceeded {
|
||||
//context deadline exceeded, replace returned error message
|
||||
err = errors.Errorf("Debug timed out: use --timeout to add more time")
|
||||
}
|
||||
}()
|
||||
|
||||
if err = d.processParams(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(clic.Args()) > 0 {
|
||||
op.Errorf("Unknown argument: %s", clic.Args()[0])
|
||||
return errors.New("invalid CLI arguments")
|
||||
}
|
||||
|
||||
op.Info("### Configuring VCH for debug ####")
|
||||
|
||||
validator, err := validate.NewValidator(op, d.Data)
|
||||
|
||||
if err != nil {
|
||||
op.Errorf("Debug cannot continue - failed to create validator: %s", err)
|
||||
return errors.New("debug failed")
|
||||
}
|
||||
defer validator.Session.Logout(op)
|
||||
|
||||
_, err = validator.ValidateTarget(op, d.Data)
|
||||
if err != nil {
|
||||
op.Errorf("Debug cannot continue - target validation failed: %s", err)
|
||||
return errors.New("debug failed")
|
||||
}
|
||||
|
||||
executor := management.NewDispatcher(validator.Context, validator.Session, nil, d.Force)
|
||||
|
||||
var vch *vm.VirtualMachine
|
||||
if d.Data.ID != "" {
|
||||
vch, err = executor.NewVCHFromID(d.Data.ID)
|
||||
} else {
|
||||
vch, err = executor.NewVCHFromComputePath(d.Data.ComputeResourcePath, d.Data.DisplayName, validator)
|
||||
}
|
||||
if err != nil {
|
||||
op.Errorf("Failed to get Virtual Container Host %s", d.DisplayName)
|
||||
op.Error(err)
|
||||
return errors.New("debug failed")
|
||||
}
|
||||
|
||||
op.Infof("")
|
||||
op.Infof("VCH ID: %s", vch.Reference().String())
|
||||
|
||||
vchConfig, err := executor.GetVCHConfig(vch)
|
||||
if err != nil {
|
||||
op.Error("Failed to get Virtual Container Host configuration")
|
||||
op.Error(err)
|
||||
return errors.New("debug failed")
|
||||
}
|
||||
|
||||
installerVer := version.GetBuild()
|
||||
|
||||
op.Info("")
|
||||
op.Infof("Installer version: %s", installerVer.ShortVersion())
|
||||
op.Infof("VCH version: %s", vchConfig.Version.ShortVersion())
|
||||
|
||||
// load the key file if set
|
||||
var key []byte
|
||||
if d.authorizedKey != "" {
|
||||
key, err = ioutil.ReadFile(d.authorizedKey)
|
||||
if err != nil {
|
||||
op.Errorf("Unable to read public key from %s: %s", d.authorizedKey, err)
|
||||
return errors.New("unable to load public key")
|
||||
}
|
||||
}
|
||||
|
||||
if err = executor.DebugVCH(vch, vchConfig, d.password, string(key)); err != nil {
|
||||
executor.CollectDiagnosticLogs()
|
||||
op.Errorf("%s", err)
|
||||
return errors.New("debug failed")
|
||||
}
|
||||
|
||||
// display the VCH endpoints again for convenience
|
||||
if err = executor.InspectVCH(vch, vchConfig, ""); err != nil {
|
||||
executor.CollectDiagnosticLogs()
|
||||
op.Errorf("%s", err)
|
||||
return errors.New("debug failed")
|
||||
}
|
||||
|
||||
op.Infof("Completed successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
172
vendor/github.com/vmware/vic/cmd/vic-machine/delete/delete.go
generated
vendored
Normal file
172
vendor/github.com/vmware/vic/cmd/vic-machine/delete/delete.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package delete
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/lib/install/management"
|
||||
"github.com/vmware/vic/lib/install/validate"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// Delete has all input parameters for vic-machine delete command
|
||||
type Uninstall struct {
|
||||
*data.Data
|
||||
|
||||
executor *management.Dispatcher
|
||||
}
|
||||
|
||||
func NewUninstall() *Uninstall {
|
||||
d := &Uninstall{}
|
||||
d.Data = data.NewData()
|
||||
return d
|
||||
}
|
||||
|
||||
// Flags return all cli flags for delete
|
||||
func (d *Uninstall) Flags() []cli.Flag {
|
||||
util := []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "Force the deletion",
|
||||
Destination: &d.Force,
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Value: 3 * time.Minute,
|
||||
Usage: "Time to wait for delete",
|
||||
Destination: &d.Timeout,
|
||||
},
|
||||
}
|
||||
|
||||
target := d.TargetFlags()
|
||||
id := d.IDFlags()
|
||||
compute := d.ComputeFlags()
|
||||
debug := d.DebugFlags(true)
|
||||
|
||||
// flag arrays are declared, now combined
|
||||
var flags []cli.Flag
|
||||
for _, f := range [][]cli.Flag{target, id, compute, util, debug} {
|
||||
flags = append(flags, f...)
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func (d *Uninstall) processParams(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if err := d.HasCredentials(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Uninstall) Run(clic *cli.Context) (err error) {
|
||||
op := common.NewOperation(clic, d.Debug.Debug)
|
||||
defer func() {
|
||||
// urfave/cli will print out exit in error handling, so no more information in main method can be printed out.
|
||||
err = common.LogErrorIfAny(op, clic, err)
|
||||
}()
|
||||
op, cancel := trace.WithTimeout(&op, d.Timeout, clic.App.Name)
|
||||
defer cancel()
|
||||
defer func() {
|
||||
if op.Err() != nil && op.Err() == context.DeadlineExceeded {
|
||||
//context deadline exceeded, replace returned error message
|
||||
err = errors.Errorf("Delete timed out: use --timeout to add more time")
|
||||
}
|
||||
}()
|
||||
|
||||
if err = d.processParams(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(clic.Args()) > 0 {
|
||||
op.Errorf("Unknown argument: %s", clic.Args()[0])
|
||||
return errors.New("invalid CLI arguments")
|
||||
}
|
||||
|
||||
op.Infof("### Removing VCH ####")
|
||||
|
||||
validator, err := validate.NewValidator(op, d.Data)
|
||||
if err != nil {
|
||||
op.Errorf("Delete cannot continue - failed to create validator: %s", err)
|
||||
return errors.New("delete failed")
|
||||
}
|
||||
defer validator.Session.Logout(op)
|
||||
_, err = validator.ValidateTarget(op, d.Data)
|
||||
if err != nil {
|
||||
op.Errorf("Delete cannot continue - target validation failed: %s", err)
|
||||
return errors.New("delete failed")
|
||||
}
|
||||
|
||||
executor := management.NewDispatcher(validator.Context, validator.Session, nil, d.Force)
|
||||
|
||||
var vch *vm.VirtualMachine
|
||||
if d.Data.ID != "" {
|
||||
vch, err = executor.NewVCHFromID(d.Data.ID)
|
||||
} else {
|
||||
vch, err = executor.NewVCHFromComputePath(d.Data.ComputeResourcePath, d.Data.DisplayName, validator)
|
||||
}
|
||||
if err != nil {
|
||||
op.Errorf("Failed to get Virtual Container Host %s", d.DisplayName)
|
||||
op.Error(err)
|
||||
return errors.New("delete failed")
|
||||
}
|
||||
|
||||
op.Infof("")
|
||||
op.Infof("VCH ID: %s", vch.Reference().String())
|
||||
|
||||
vchConfig, err := executor.GetNoSecretVCHConfig(vch)
|
||||
if err != nil {
|
||||
op.Error("Failed to get Virtual Container Host configuration")
|
||||
op.Error(err)
|
||||
return errors.New("delete failed")
|
||||
}
|
||||
|
||||
// compare vch version and vic-machine version
|
||||
installerBuild := version.GetBuild()
|
||||
if vchConfig.Version == nil || !installerBuild.Equal(vchConfig.Version) {
|
||||
if !d.Data.Force {
|
||||
op.Errorf("VCH version %q is different than installer version %s. Upgrade VCH before deleting or specify --force to force delete", vchConfig.Version.ShortVersion(), installerBuild.ShortVersion())
|
||||
return errors.New("delete failed")
|
||||
}
|
||||
|
||||
op.Warnf("VCH version %q is different than installer version %s. Force delete will attempt to remove everything related to the installed VCH", vchConfig.Version.ShortVersion(), installerBuild.ShortVersion())
|
||||
}
|
||||
|
||||
if err = executor.DeleteVCH(vchConfig, nil, nil); err != nil {
|
||||
executor.CollectDiagnosticLogs()
|
||||
op.Errorf("%s", err)
|
||||
return errors.New("delete failed")
|
||||
}
|
||||
|
||||
op.Info("----------")
|
||||
op.Info("If firewall changes were made for VIC during install, they were not reverted during delete")
|
||||
op.Info("To modify firewall rules see vic-machine update firewall --help")
|
||||
op.Info("----------")
|
||||
op.Info("Completed successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
342
vendor/github.com/vmware/vic/cmd/vic-machine/inspect/inspect.go
generated
vendored
Normal file
342
vendor/github.com/vmware/vic/cmd/vic-machine/inspect/inspect.go
generated
vendored
Normal file
@@ -0,0 +1,342 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package inspect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"github.com/vmware/vic/cmd/vic-machine/converter"
|
||||
"github.com/vmware/vic/cmd/vic-machine/create"
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/lib/install/management"
|
||||
"github.com/vmware/vic/lib/install/validate"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// Inspect has all input parameters for vic-machine inspect command
|
||||
type Inspect struct {
|
||||
*data.Data
|
||||
|
||||
CertPath string
|
||||
|
||||
executor *management.Dispatcher
|
||||
|
||||
Format string
|
||||
}
|
||||
|
||||
type state struct {
|
||||
i *Inspect
|
||||
op trace.Operation
|
||||
validator *validate.Validator
|
||||
vchConfig *config.VirtualContainerHostConfigSpec
|
||||
vch *vm.VirtualMachine
|
||||
executor *management.Dispatcher
|
||||
}
|
||||
|
||||
type command func(state) error
|
||||
|
||||
func NewInspect() *Inspect {
|
||||
d := &Inspect{}
|
||||
d.Data = data.NewData()
|
||||
return d
|
||||
}
|
||||
|
||||
// Flags returns all cli flags for inspect
|
||||
func (i *Inspect) Flags() []cli.Flag {
|
||||
util := []cli.Flag{
|
||||
cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Value: 3 * time.Minute,
|
||||
Usage: "Time to wait for inspect",
|
||||
Destination: &i.Timeout,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tls-cert-path",
|
||||
Value: "",
|
||||
Usage: "The path to check for existing certificates. Defaults to './<vch name>/'",
|
||||
Destination: &i.CertPath,
|
||||
},
|
||||
}
|
||||
|
||||
target := i.TargetFlags()
|
||||
id := i.IDFlags()
|
||||
compute := i.ComputeFlags()
|
||||
debug := i.DebugFlags(true)
|
||||
|
||||
// flag arrays are declared, now combined
|
||||
var flags []cli.Flag
|
||||
for _, f := range [][]cli.Flag{target, id, compute, util, debug} {
|
||||
flags = append(flags, f...)
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func (i *Inspect) ConfigFlags() []cli.Flag {
|
||||
config := cli.StringFlag{
|
||||
Name: "format",
|
||||
Value: "verbose",
|
||||
Usage: "Determine the format of configuration output. Supported formats: raw, verbose",
|
||||
Destination: &i.Format,
|
||||
}
|
||||
flags := []cli.Flag{config}
|
||||
flags = append(flags, i.Flags()...)
|
||||
return flags
|
||||
}
|
||||
|
||||
func (i *Inspect) processParams(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if err := i.HasCredentials(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Inspect) run(clic *cli.Context, op trace.Operation, cmd command) (err error) {
|
||||
defer func() {
|
||||
// urfave/cli will print out exit in error handling, so no more information in main method can be printed out.
|
||||
err = common.LogErrorIfAny(op, clic, err)
|
||||
}()
|
||||
op, cancel := trace.WithTimeout(&op, i.Timeout, clic.App.Name)
|
||||
defer cancel()
|
||||
defer func() {
|
||||
if op.Err() != nil && op.Err() == context.DeadlineExceeded {
|
||||
//context deadline exceeded, replace returned error message
|
||||
err = errors.Errorf("Inspect timed out: use --timeout to add more time")
|
||||
}
|
||||
}()
|
||||
|
||||
if err = i.processParams(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(clic.Args()) > 0 {
|
||||
op.Errorf("Unknown argument: %s", clic.Args()[0])
|
||||
return errors.New("invalid CLI arguments")
|
||||
}
|
||||
|
||||
op.Infof("### Inspecting VCH ####")
|
||||
|
||||
validator, err := validate.NewValidator(op, i.Data)
|
||||
|
||||
if err != nil {
|
||||
op.Errorf("Inspect cannot continue - failed to create validator: %s", err)
|
||||
return errors.New("inspect failed")
|
||||
}
|
||||
defer validator.Session.Logout(op)
|
||||
|
||||
_, err = validator.ValidateTarget(op, i.Data)
|
||||
if err != nil {
|
||||
op.Errorf("Inspect cannot continue - target validation failed: %s", err)
|
||||
return errors.New("inspect failed")
|
||||
}
|
||||
|
||||
executor := management.NewDispatcher(validator.Context, validator.Session, nil, i.Force)
|
||||
|
||||
var vch *vm.VirtualMachine
|
||||
if i.Data.ID != "" {
|
||||
vch, err = executor.NewVCHFromID(i.Data.ID)
|
||||
} else {
|
||||
vch, err = executor.NewVCHFromComputePath(i.Data.ComputeResourcePath, i.Data.DisplayName, validator)
|
||||
}
|
||||
if err != nil {
|
||||
op.Errorf("Failed to get Virtual Container Host %s", i.DisplayName)
|
||||
op.Error(err)
|
||||
return errors.New("inspect failed")
|
||||
}
|
||||
|
||||
vchConfig, err := executor.GetNoSecretVCHConfig(vch)
|
||||
if err != nil {
|
||||
op.Error("Failed to get Virtual Container Host configuration")
|
||||
op.Error(err)
|
||||
return errors.New("inspect failed")
|
||||
}
|
||||
|
||||
return cmd(state{i, op, validator, vchConfig, vch, executor})
|
||||
}
|
||||
|
||||
func (i *Inspect) RunConfig(clic *cli.Context) (err error) {
|
||||
op := common.NewOperation(clic, i.Debug.Debug)
|
||||
|
||||
if i.Format == "raw" {
|
||||
op.Logger.Level = logrus.ErrorLevel
|
||||
op.Logger.Out = os.Stderr
|
||||
} else if i.Format != "verbose" {
|
||||
op.Warnf("Invalid configuration output format '%s'. Valid options are raw, verbose.", i.Format)
|
||||
op.Warn("Using verbose configuration format")
|
||||
i.Format = "verbose"
|
||||
}
|
||||
|
||||
return i.run(clic, op, func(s state) error {
|
||||
err = i.showConfiguration(s.op, s.validator.Session.Finder, s.vchConfig, s.vch)
|
||||
if err != nil {
|
||||
op.Error("Failed to print Virtual Container Host configuration")
|
||||
op.Error(err)
|
||||
return errors.New("inspect failed")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Inspect) Run(clic *cli.Context) (err error) {
|
||||
op := common.NewOperation(clic, i.Debug.Debug)
|
||||
|
||||
return i.run(clic, op, func(s state) error {
|
||||
op.Infof("")
|
||||
op.Infof("VCH ID: %s", s.vch.Reference().String())
|
||||
|
||||
installerVer := version.GetBuild()
|
||||
|
||||
op.Info("")
|
||||
op.Infof("Installer version: %s", installerVer.ShortVersion())
|
||||
op.Infof("VCH version: %s", s.vchConfig.Version.ShortVersion())
|
||||
op.Info("")
|
||||
op.Info("VCH upgrade status:")
|
||||
i.upgradeStatusMessage(s.op, s.vch, installerVer, s.vchConfig.Version)
|
||||
|
||||
if err = s.executor.InspectVCH(s.vch, s.vchConfig, i.CertPath); err != nil {
|
||||
s.executor.CollectDiagnosticLogs()
|
||||
op.Errorf("%s", err)
|
||||
return errors.New("inspect failed")
|
||||
}
|
||||
|
||||
op.Infof("Completed successfully")
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func retrieveMapOptions(op trace.Operation, finder validate.Finder,
|
||||
conf *config.VirtualContainerHostConfigSpec, vm *vm.VirtualMachine) (map[string][]string, error) {
|
||||
data, err := validate.NewDataFromConfig(op, finder, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = validate.SetDataFromVM(op, finder, vm, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return converter.DataToOption(data)
|
||||
}
|
||||
|
||||
func (i Inspect) showConfiguration(op trace.Operation, finder validate.Finder, conf *config.VirtualContainerHostConfigSpec, vm *vm.VirtualMachine) error {
|
||||
mapOptions, err := retrieveMapOptions(op, finder, conf, vm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options := i.sortedOutput(mapOptions)
|
||||
if i.Format == "raw" {
|
||||
strOptions := strings.Join(options, " ")
|
||||
fmt.Println(strOptions)
|
||||
} else if i.Format == "verbose" {
|
||||
strOptions := strings.Join(options, "\n\t")
|
||||
op.Info("")
|
||||
op.Infof("Target VCH created with the following options: \n\n\t%s\n", strOptions)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Inspect) sortedOutput(mapOptions map[string][]string) (output []string) {
|
||||
create := create.NewCreate()
|
||||
cFlags := create.Flags()
|
||||
for _, f := range cFlags {
|
||||
key := f.GetName()
|
||||
// change multiple option name to long name: e.g. from target,t => target
|
||||
s := strings.Split(key, ",")
|
||||
if len(s) > 1 {
|
||||
key = s[0]
|
||||
}
|
||||
|
||||
values, ok := mapOptions[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
defaultValue := ""
|
||||
switch t := f.(type) {
|
||||
case cli.StringFlag:
|
||||
defaultValue = t.Value
|
||||
case cli.IntFlag:
|
||||
defaultValue = strconv.Itoa(t.Value)
|
||||
}
|
||||
for _, val := range values {
|
||||
if val == defaultValue {
|
||||
// do not print command option if it's same to default
|
||||
continue
|
||||
}
|
||||
output = append(output, fmt.Sprintf("--%s=%s", key, val))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// upgradeStatusMessage generates a user facing status string about upgrade progress and status
|
||||
func (i *Inspect) upgradeStatusMessage(op trace.Operation, vch *vm.VirtualMachine, installerVer *version.Build, vchVer *version.Build) {
|
||||
if sameVer := installerVer.Equal(vchVer); sameVer {
|
||||
op.Info("Installer has same version as VCH")
|
||||
op.Info("No upgrade available with this installer version")
|
||||
return
|
||||
}
|
||||
|
||||
upgrading, err := vch.VCHUpdateStatus(op)
|
||||
if err != nil {
|
||||
op.Errorf("Unable to determine if upgrade/configure is in progress: %s", err)
|
||||
return
|
||||
}
|
||||
if upgrading {
|
||||
op.Info("Upgrade/configure in progress")
|
||||
return
|
||||
}
|
||||
|
||||
canUpgrade, err := installerVer.IsNewer(vchVer)
|
||||
if err != nil {
|
||||
op.Errorf("Unable to determine if upgrade is available: %s", err)
|
||||
return
|
||||
}
|
||||
if canUpgrade {
|
||||
op.Info("Upgrade available")
|
||||
return
|
||||
}
|
||||
|
||||
oldInstaller, err := installerVer.IsOlder(vchVer)
|
||||
if err != nil {
|
||||
op.Errorf("Unable to determine if upgrade is available: %s", err)
|
||||
return
|
||||
}
|
||||
if oldInstaller {
|
||||
op.Info("Installer has older version than VCH")
|
||||
op.Info("No upgrade available with this installer version")
|
||||
return
|
||||
}
|
||||
|
||||
// can't get here
|
||||
op.Warn("Invalid upgrade status")
|
||||
return
|
||||
}
|
||||
219
vendor/github.com/vmware/vic/cmd/vic-machine/list/list.go
generated
vendored
Normal file
219
vendor/github.com/vmware/vic/cmd/vic-machine/list/list.go
generated
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package list
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/lib/install/management"
|
||||
"github.com/vmware/vic/lib/install/validate"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
type items struct {
|
||||
ID string
|
||||
Path string
|
||||
Name string
|
||||
Version string
|
||||
UpgradeStatus string
|
||||
}
|
||||
|
||||
// templ is parsed by text/template package
|
||||
const templ = `{{range .}}
|
||||
{{.ID}} {{.Path}} {{.Name}} {{.Version}} {{.UpgradeStatus}}{{end}}
|
||||
`
|
||||
|
||||
// List has all input parameters for vic-machine ls command
|
||||
type List struct {
|
||||
*data.Data
|
||||
|
||||
executor *management.Dispatcher
|
||||
}
|
||||
|
||||
func NewList() *List {
|
||||
d := &List{}
|
||||
d.Data = data.NewData()
|
||||
return d
|
||||
}
|
||||
|
||||
// Flags return all cli flags for ls
|
||||
func (l *List) Flags() []cli.Flag {
|
||||
util := []cli.Flag{
|
||||
cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Value: 3 * time.Minute,
|
||||
Usage: "Time to wait for list",
|
||||
Destination: &l.Timeout,
|
||||
},
|
||||
}
|
||||
|
||||
target := l.TargetFlags()
|
||||
// TODO: why not allow name as a filter, like most list operations
|
||||
compute := l.ComputeFlagsNoName()
|
||||
debug := l.DebugFlags(true)
|
||||
|
||||
// flag arrays are declared, now combined
|
||||
var flags []cli.Flag
|
||||
for _, f := range [][]cli.Flag{target, compute, util, debug} {
|
||||
flags = append(flags, f...)
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func (l *List) processParams(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if err := l.HasCredentials(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *List) prettyPrint(op trace.Operation, cli *cli.Context, vchs []*vm.VirtualMachine, executor *management.Dispatcher) {
|
||||
data := []items{
|
||||
{"ID", "PATH", "NAME", "VERSION", "UPGRADE STATUS"},
|
||||
}
|
||||
installerVer := version.GetBuild()
|
||||
for _, vch := range vchs {
|
||||
|
||||
vchConfig, err := executor.GetNoSecretVCHConfig(vch)
|
||||
var version string
|
||||
if err != nil {
|
||||
op.Error("Failed to get Virtual Container Host configuration")
|
||||
op.Error(err)
|
||||
version = "unknown"
|
||||
} else {
|
||||
version = vchConfig.Version.ShortVersion()
|
||||
}
|
||||
|
||||
parentPath := path.Dir(path.Dir(vch.InventoryPath))
|
||||
name := path.Base(vch.InventoryPath)
|
||||
upgradeStatus := l.upgradeStatusMessage(op, vch, installerVer, vchConfig.Version)
|
||||
data = append(data,
|
||||
items{vch.Reference().Value, parentPath, name, version, upgradeStatus})
|
||||
}
|
||||
t := template.New("vic-machine ls")
|
||||
// #nosec: Errors unhandled.
|
||||
t, _ = t.Parse(templ)
|
||||
w := tabwriter.NewWriter(cli.App.Writer, 8, 8, 8, ' ', 0)
|
||||
if err := t.Execute(w, data); err != nil {
|
||||
op.Fatal(err)
|
||||
}
|
||||
// #nosec: Errors unhandled.
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func (l *List) Run(clic *cli.Context) (err error) {
|
||||
op := common.NewOperation(clic, l.Debug.Debug)
|
||||
defer func() {
|
||||
// urfave/cli will print out exit in error handling, so no more information in main method can be printed out.
|
||||
err = common.LogErrorIfAny(op, clic, err)
|
||||
}()
|
||||
op, cancel := trace.WithTimeout(&op, l.Timeout, clic.App.Name)
|
||||
defer cancel()
|
||||
defer func() {
|
||||
if op.Err() != nil && op.Err() == context.DeadlineExceeded {
|
||||
//context deadline exceeded, replace returned error message
|
||||
err = errors.Errorf("List timed out: use --timeout to add more time")
|
||||
}
|
||||
}()
|
||||
|
||||
if err = l.processParams(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(clic.Args()) > 0 {
|
||||
op.Errorf("Unknown argument: %s", clic.Args()[0])
|
||||
return errors.New("invalid CLI arguments")
|
||||
}
|
||||
|
||||
op.Infof("### Listing VCHs ####")
|
||||
|
||||
var validator *validate.Validator
|
||||
if validator, err = validate.NewValidator(op, l.Data); err != nil {
|
||||
op.Errorf("List cannot continue - failed to create validator: %s", err)
|
||||
return errors.New("list failed")
|
||||
}
|
||||
defer validator.Session.Logout(op)
|
||||
|
||||
// If dc is not set, and multiple datacenter is available, vic-machine ls will list VCHs under all datacenters.
|
||||
validator.AllowEmptyDC()
|
||||
|
||||
_, err = validator.ValidateTarget(op, l.Data)
|
||||
if err != nil {
|
||||
op.Errorf("List cannot continue - target validation failed: %s", err)
|
||||
return errors.New("list failed")
|
||||
}
|
||||
_, err = validator.ValidateCompute(op, l.Data, false)
|
||||
if err != nil {
|
||||
op.Errorf("List cannot continue - compute resource validation failed: %s", err)
|
||||
return errors.New("list failed")
|
||||
}
|
||||
executor := management.NewDispatcher(validator.Context, validator.Session, nil, false)
|
||||
vchs, err := executor.SearchVCHs(validator.ClusterPath)
|
||||
if err != nil {
|
||||
op.Errorf("List cannot continue - failed to search VCHs in %s: %s", validator.ResourcePoolPath, err)
|
||||
}
|
||||
l.prettyPrint(op, clic, vchs, executor)
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeStatusMessage generates a user facing status string about upgrade progress and status
|
||||
func (l *List) upgradeStatusMessage(op trace.Operation, vch *vm.VirtualMachine, installerVer *version.Build, vchVer *version.Build) string {
|
||||
if sameVer := installerVer.Equal(vchVer); sameVer {
|
||||
return "Up to date"
|
||||
}
|
||||
|
||||
upgrading, err := vch.VCHUpdateStatus(op)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Unknown: %s", err)
|
||||
}
|
||||
if upgrading {
|
||||
return "Upgrade in progress"
|
||||
}
|
||||
|
||||
canUpgrade, err := installerVer.IsNewer(vchVer)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Unknown: %s", err)
|
||||
}
|
||||
if canUpgrade {
|
||||
return fmt.Sprintf("Upgradeable to %s", installerVer.ShortVersion())
|
||||
}
|
||||
|
||||
oldInstaller, err := installerVer.IsOlder(vchVer)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Unknown: %s", err)
|
||||
}
|
||||
if oldInstaller {
|
||||
return fmt.Sprintf("VCH has newer version")
|
||||
}
|
||||
|
||||
// can't get here
|
||||
return "Invalid upgrade status"
|
||||
}
|
||||
178
vendor/github.com/vmware/vic/cmd/vic-machine/main.go
generated
vendored
Normal file
178
vendor/github.com/vmware/vic/cmd/vic-machine/main.go
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
runtime "runtime/debug"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-machine/configure"
|
||||
"github.com/vmware/vic/cmd/vic-machine/create"
|
||||
"github.com/vmware/vic/cmd/vic-machine/debug"
|
||||
uninstall "github.com/vmware/vic/cmd/vic-machine/delete"
|
||||
"github.com/vmware/vic/cmd/vic-machine/inspect"
|
||||
"github.com/vmware/vic/cmd/vic-machine/list"
|
||||
"github.com/vmware/vic/cmd/vic-machine/update"
|
||||
"github.com/vmware/vic/cmd/vic-machine/upgrade"
|
||||
viclog "github.com/vmware/vic/pkg/log"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
)
|
||||
|
||||
const (
|
||||
LogFile = "vic-machine.log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Name = filepath.Base(os.Args[0])
|
||||
app.Usage = "Create and manage Virtual Container Hosts"
|
||||
app.EnableBashCompletion = true
|
||||
|
||||
create := create.NewCreate()
|
||||
uninstall := uninstall.NewUninstall()
|
||||
inspect := inspect.NewInspect()
|
||||
list := list.NewList()
|
||||
upgrade := upgrade.NewUpgrade()
|
||||
debug := debug.NewDebug()
|
||||
updateFw := update.NewUpdateFw()
|
||||
configure := configure.NewConfigure()
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "create",
|
||||
Usage: "Deploy VCH",
|
||||
Action: create.Run,
|
||||
Flags: create.Flags(),
|
||||
},
|
||||
{
|
||||
Name: "configure",
|
||||
Usage: "Update VCH configuration",
|
||||
Action: configure.Run,
|
||||
Flags: configure.Flags(),
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Usage: "Delete VCH and associated resources",
|
||||
Action: uninstall.Run,
|
||||
Flags: uninstall.Flags(),
|
||||
},
|
||||
{
|
||||
Name: "ls",
|
||||
Usage: "List VCHs",
|
||||
Action: list.Run,
|
||||
Flags: list.Flags(),
|
||||
},
|
||||
{
|
||||
Name: "inspect",
|
||||
Usage: "Inspect VCH",
|
||||
Action: inspect.Run,
|
||||
Flags: inspect.Flags(),
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "config",
|
||||
Usage: "Show VCH configuration options",
|
||||
Action: inspect.RunConfig,
|
||||
Flags: inspect.ConfigFlags(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "upgrade",
|
||||
Usage: "Upgrade VCH to latest version",
|
||||
Action: upgrade.Run,
|
||||
Flags: upgrade.Flags(),
|
||||
},
|
||||
{
|
||||
Name: "version",
|
||||
Usage: "Show VIC version information",
|
||||
Action: showVersion,
|
||||
},
|
||||
{
|
||||
Name: "debug",
|
||||
Usage: "Debug VCH",
|
||||
Action: debug.Run,
|
||||
Flags: debug.Flags(),
|
||||
},
|
||||
{
|
||||
Name: "update",
|
||||
Usage: "Modify configuration",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "firewall",
|
||||
Usage: "Modify host firewall",
|
||||
Action: updateFw.Run,
|
||||
Flags: updateFw.Flags(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Version = version.GetBuild().ShortVersion()
|
||||
|
||||
logs := []io.Writer{app.Writer}
|
||||
// Open log file
|
||||
// #nosec: Expect file permissions to be 0600 or less
|
||||
f, err := os.OpenFile(LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
// #nosec: Errors unhandled.
|
||||
fmt.Fprintf(os.Stderr, "Error opening logfile %s: %v\n", LogFile, err)
|
||||
} else {
|
||||
defer f.Close()
|
||||
logs = append(logs, f)
|
||||
}
|
||||
|
||||
// Initiliaze logger with default TextFormatter
|
||||
log.SetFormatter(viclog.NewTextFormatter())
|
||||
// SetOutput to io.MultiWriter so that we can log to stdout and a file
|
||||
log.SetOutput(io.MultiWriter(logs...))
|
||||
// Set up the global trace logger to write to the file
|
||||
trace.Logger.Out = f
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Output instructions to check log file for details to the user, but not the log file
|
||||
logger := log.New()
|
||||
logger.Out = app.Writer
|
||||
logger.Errorf("--------------------")
|
||||
logger.Errorf("%s failed, please check log file %s for details", app.Name, LogFile)
|
||||
|
||||
// Output the stack to the log file, but not the user
|
||||
// #nosec: Errors unhandled.
|
||||
fmt.Fprintf(f, "%s", runtime.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
// When writing log files (vic-machine.log and the datastore log), always log at debug.
|
||||
defer func(old log.Level) {
|
||||
trace.Logger.Level = old
|
||||
}(trace.Logger.Level)
|
||||
trace.Logger.Level = log.DebugLevel
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
app.Run(os.Args)
|
||||
}
|
||||
|
||||
func showVersion(cli *cli.Context) error {
|
||||
// #nosec: Errors unhandled.
|
||||
fmt.Fprintf(cli.App.Writer, "%v version %v\n", cli.App.Name, cli.App.Version)
|
||||
return nil
|
||||
}
|
||||
185
vendor/github.com/vmware/vic/cmd/vic-machine/update/firewall.go
generated
vendored
Normal file
185
vendor/github.com/vmware/vic/cmd/vic-machine/update/firewall.go
generated
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package update
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/lib/install/management"
|
||||
"github.com/vmware/vic/lib/install/validate"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// UpdateFw has all input parameters for vic-machine update firewall command
|
||||
type UpdateFw struct {
|
||||
*data.Data
|
||||
|
||||
executor *management.Dispatcher
|
||||
|
||||
enableFw bool
|
||||
disableFw bool
|
||||
}
|
||||
|
||||
func NewUpdateFw() *UpdateFw {
|
||||
i := &UpdateFw{}
|
||||
i.Data = data.NewData()
|
||||
return i
|
||||
}
|
||||
|
||||
// Flags return all cli flags for update firewall
|
||||
func (i *UpdateFw) Flags() []cli.Flag {
|
||||
update := []cli.Flag{
|
||||
cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Value: 3 * time.Minute,
|
||||
Usage: "Time to wait for update firewall",
|
||||
Destination: &i.Timeout,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "allow",
|
||||
Usage: "Enable a firewall rule on target host(s) to allow VIC communication",
|
||||
Destination: &i.enableFw,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "deny",
|
||||
Usage: "Disable the firewall rule on target host(s) that allows VIC communication",
|
||||
Destination: &i.disableFw,
|
||||
},
|
||||
}
|
||||
|
||||
target := i.TargetFlags()
|
||||
compute := i.ComputeFlagsNoName()
|
||||
debug := i.DebugFlags(true)
|
||||
|
||||
// flag arrays are declared, now combined
|
||||
var flags []cli.Flag
|
||||
for _, f := range [][]cli.Flag{target, compute, update, debug} {
|
||||
flags = append(flags, f...)
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func (i *UpdateFw) processParams(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if err := i.HasCredentials(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if i.enableFw && i.disableFw {
|
||||
return errors.New("Only one of --allow and --deny can be set")
|
||||
}
|
||||
|
||||
if !i.enableFw && !i.disableFw {
|
||||
return errors.New("No command selected")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *UpdateFw) Run(clic *cli.Context) (err error) {
|
||||
op := common.NewOperation(clic, i.Debug.Debug)
|
||||
defer func() {
|
||||
// urfave/cli will print out exit in error handling, so no more information in main method can be printed out.
|
||||
err = common.LogErrorIfAny(op, clic, err)
|
||||
}()
|
||||
op, cancel := trace.WithTimeout(&op, i.Timeout, clic.App.Name)
|
||||
defer cancel()
|
||||
defer func() {
|
||||
if op.Err() != nil && op.Err() == context.DeadlineExceeded {
|
||||
//context deadline exceeded, replace returned error message
|
||||
err = errors.Errorf("Update timed out: use --timeout to add more time")
|
||||
}
|
||||
}()
|
||||
|
||||
if err = i.processParams(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(clic.Args()) > 0 {
|
||||
op.Errorf("Unknown argument: %s", clic.Args()[0])
|
||||
return errors.New("invalid CLI arguments")
|
||||
}
|
||||
|
||||
op.Infof("### Updating Firewall ####")
|
||||
|
||||
var validator *validate.Validator
|
||||
if validator, err = validate.NewValidator(op, i.Data); err != nil {
|
||||
op.Errorf("Update cannot continue - failed to create validator: %s", err)
|
||||
return errors.New("update firewall failed")
|
||||
}
|
||||
defer validator.Session.Logout(op)
|
||||
|
||||
_, err = validator.ValidateTarget(op, i.Data)
|
||||
if err != nil {
|
||||
op.Errorf("Update cannot continue - target validation failed: %s", err)
|
||||
return errors.New("update firewall failed")
|
||||
}
|
||||
_, err = validator.ValidateCompute(op, i.Data, true)
|
||||
if err != nil {
|
||||
op.Errorf("Update cannot continue - compute resource validation failed: %s", err)
|
||||
return errors.New("update firewall failed")
|
||||
}
|
||||
|
||||
executor := management.NewDispatcher(validator.Context, validator.Session, nil, false)
|
||||
|
||||
if i.enableFw {
|
||||
op.Info("")
|
||||
op.Warn("### WARNING ###")
|
||||
op.Warn("\tThis command modifies the host firewall on the target machine or cluster")
|
||||
op.Warnf("\tThe ruleset %q will be enabled", management.RulesetID)
|
||||
op.Warn("\tThis allows all outbound TCP traffic from the target")
|
||||
op.Warn("\tTo undo this modification use --deny")
|
||||
op.Info("")
|
||||
|
||||
err := executor.EnableFirewallRuleset()
|
||||
if err != nil {
|
||||
op.Errorf("Failed to enable VIC firewall rule: %s", err)
|
||||
return errors.New("failed to enable firewall rule")
|
||||
}
|
||||
}
|
||||
|
||||
if i.disableFw {
|
||||
op.Info("")
|
||||
op.Warn("### WARNING ###")
|
||||
op.Warn("\tThis command modifies the host firewall on the target machine or cluster")
|
||||
op.Warnf("\tThe ruleset %q will be disabled", management.RulesetID)
|
||||
op.Warn("\tThis disables the ruleset that allows all outbound TCP traffic from the target")
|
||||
op.Warn("\tVIC Engine will not function unless 2377/tcp outbound is allowed")
|
||||
op.Warn("\tTo undo this modification use --allow")
|
||||
op.Info("")
|
||||
|
||||
err := executor.DisableFirewallRuleset()
|
||||
if err != nil {
|
||||
op.Errorf("Failed to disable VIC firewall rule: %s", err)
|
||||
return errors.New("failed to disable firewall rule")
|
||||
}
|
||||
}
|
||||
op.Info("")
|
||||
|
||||
if i.enableFw || i.disableFw {
|
||||
op.Infof("Firewall changes complete")
|
||||
}
|
||||
|
||||
op.Infof("Command completed successfully")
|
||||
return nil
|
||||
}
|
||||
228
vendor/github.com/vmware/vic/cmd/vic-machine/upgrade/upgrade.go
generated
vendored
Normal file
228
vendor/github.com/vmware/vic/cmd/vic-machine/upgrade/upgrade.go
generated
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package upgrade
|
||||
|
||||
import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"github.com/vmware/vic/lib/install/data"
|
||||
"github.com/vmware/vic/lib/install/management"
|
||||
"github.com/vmware/vic/lib/install/validate"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// Upgrade has all input parameters for vic-machine upgrade command
|
||||
type Upgrade struct {
|
||||
*data.Data
|
||||
|
||||
executor *management.Dispatcher
|
||||
}
|
||||
|
||||
func NewUpgrade() *Upgrade {
|
||||
upgrade := &Upgrade{}
|
||||
upgrade.Data = data.NewData()
|
||||
|
||||
return upgrade
|
||||
}
|
||||
|
||||
// Flags return all cli flags for upgrade
|
||||
func (u *Upgrade) Flags() []cli.Flag {
|
||||
util := []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "Force the upgrade (ignores version checks)",
|
||||
Destination: &u.Force,
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Value: 3 * time.Minute,
|
||||
Usage: "Time to wait for upgrade",
|
||||
Destination: &u.Timeout,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "rollback",
|
||||
Usage: "Roll back VCH version to before the current upgrade",
|
||||
Destination: &u.Rollback,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "reset-progress",
|
||||
Usage: "Reset the UpdateInProgress flag. Warning: Do not reset this flag if another upgrade/configure process is running",
|
||||
Destination: &u.ResetInProgressFlag,
|
||||
},
|
||||
}
|
||||
|
||||
target := u.TargetFlags()
|
||||
id := u.IDFlags()
|
||||
compute := u.ComputeFlags()
|
||||
iso := u.ImageFlags(false)
|
||||
debug := u.DebugFlags(true)
|
||||
|
||||
// flag arrays are declared, now combined
|
||||
var flags []cli.Flag
|
||||
for _, f := range [][]cli.Flag{target, id, compute, iso, util, debug} {
|
||||
flags = append(flags, f...)
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func (u *Upgrade) processParams(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if err := u.HasCredentials(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Upgrade) Run(clic *cli.Context) (err error) {
|
||||
op := common.NewOperation(clic, u.Debug.Debug)
|
||||
defer func() {
|
||||
// urfave/cli will print out exit in error handling, so no more information in main method can be printed out.
|
||||
err = common.LogErrorIfAny(op, clic, err)
|
||||
}()
|
||||
op, cancel := trace.WithCancel(&op, clic.App.Name)
|
||||
defer cancel()
|
||||
|
||||
if err = u.processParams(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(clic.Args()) > 0 {
|
||||
op.Errorf("Unknown argument: %s", clic.Args()[0])
|
||||
return errors.New("invalid CLI arguments")
|
||||
}
|
||||
|
||||
var images map[string]string
|
||||
if images, err = u.CheckImagesFiles(op, u.Force); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
op.Infof("### Upgrading VCH ####")
|
||||
|
||||
validator, err := validate.NewValidator(op, u.Data)
|
||||
if err != nil {
|
||||
op.Errorf("Upgrade cannot continue - failed to create validator: %s", err)
|
||||
return errors.New("upgrade failed")
|
||||
}
|
||||
defer validator.Session.Logout(op)
|
||||
|
||||
_, err = validator.ValidateTarget(op, u.Data)
|
||||
if err != nil {
|
||||
op.Errorf("Upgrade cannot continue - target validation failed: %s", err)
|
||||
return errors.New("upgrade failed")
|
||||
}
|
||||
executor := management.NewDispatcher(validator.Context, validator.Session, nil, u.Force)
|
||||
|
||||
var vch *vm.VirtualMachine
|
||||
if u.Data.ID != "" {
|
||||
vch, err = executor.NewVCHFromID(u.Data.ID)
|
||||
} else {
|
||||
vch, err = executor.NewVCHFromComputePath(u.Data.ComputeResourcePath, u.Data.DisplayName, validator)
|
||||
}
|
||||
if err != nil {
|
||||
op.Errorf("Failed to get Virtual Container Host %s", u.DisplayName)
|
||||
op.Error(err)
|
||||
return errors.New("upgrade failed")
|
||||
}
|
||||
|
||||
op.Infof("")
|
||||
op.Infof("VCH ID: %s", vch.Reference().String())
|
||||
|
||||
if u.ResetInProgressFlag {
|
||||
if err = vch.SetVCHUpdateStatus(op, false); err != nil {
|
||||
op.Error("Failed to reset UpdateInProgress flag")
|
||||
op.Error(err)
|
||||
return errors.New("upgrade failed")
|
||||
}
|
||||
op.Infof("Reset UpdateInProgress flag successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
upgrading, err := vch.VCHUpdateStatus(op)
|
||||
if err != nil {
|
||||
op.Error("Unable to determine if upgrade/configure is in progress")
|
||||
op.Error(err)
|
||||
return errors.New("upgrade failed")
|
||||
}
|
||||
if upgrading {
|
||||
op.Error("Upgrade failed: another upgrade/configure operation is in progress")
|
||||
op.Error("If no other upgrade/configure process is running, use --reset-progress to reset the VCH upgrade/configure status")
|
||||
return errors.New("upgrade failed")
|
||||
}
|
||||
|
||||
if err = vch.SetVCHUpdateStatus(op, true); err != nil {
|
||||
op.Error("Failed to set UpdateInProgress flag to true")
|
||||
op.Error(err)
|
||||
return errors.New("upgrade failed")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err = vch.SetVCHUpdateStatus(op, false); err != nil {
|
||||
op.Error("Failed to reset UpdateInProgress")
|
||||
op.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
vchConfig, err := executor.FetchAndMigrateVCHConfig(vch)
|
||||
if err != nil {
|
||||
op.Error("Failed to get Virtual Container Host configuration")
|
||||
op.Error(err)
|
||||
return errors.New("upgrade failed")
|
||||
}
|
||||
|
||||
vConfig := validator.AddDeprecatedFields(op, vchConfig, u.Data)
|
||||
vConfig.ImageFiles = images
|
||||
vConfig.ApplianceISO = path.Base(u.ApplianceISO)
|
||||
vConfig.BootstrapISO = path.Base(u.BootstrapISO)
|
||||
vConfig.Timeout = u.Timeout
|
||||
|
||||
// only care about versions if we're not doing a manual rollback
|
||||
if !u.Data.Rollback {
|
||||
if err := validator.AssertVersion(op, vchConfig); err != nil {
|
||||
op.Error(err)
|
||||
return errors.New("upgrade failed")
|
||||
}
|
||||
}
|
||||
|
||||
if vchConfig, err = validator.ValidateMigratedConfig(op, vchConfig); err != nil {
|
||||
op.Errorf("Failed to migrate Virtual Container Host configuration %s", u.DisplayName)
|
||||
op.Error(err)
|
||||
return errors.New("upgrade failed")
|
||||
}
|
||||
|
||||
if !u.Data.Rollback {
|
||||
err = executor.Configure(vch, vchConfig, vConfig, false)
|
||||
} else {
|
||||
err = executor.Rollback(vch, vchConfig, vConfig)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// upgrade failed
|
||||
executor.CollectDiagnosticLogs()
|
||||
return errors.New("upgrade failed")
|
||||
}
|
||||
|
||||
op.Infof("Completed successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
108
vendor/github.com/vmware/vic/cmd/vic-ui/main.go
generated
vendored
Normal file
108
vendor/github.com/vmware/vic/cmd/vic-ui/main.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-ui/ui"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
viclog "github.com/vmware/vic/pkg/log"
|
||||
)
|
||||
|
||||
var (
|
||||
Version string
|
||||
BuildID string
|
||||
CommitID string
|
||||
)
|
||||
|
||||
const (
|
||||
LogFile = "vic-ui.log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Name = filepath.Base(os.Args[0])
|
||||
app.Usage = "Install/remove VIC UI plugin"
|
||||
app.EnableBashCompletion = true
|
||||
|
||||
ui := ui.NewUI()
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "install",
|
||||
Usage: "Install UI plugin",
|
||||
Action: ui.Install,
|
||||
Flags: ui.Flags(),
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "Remove UI plugin",
|
||||
Action: ui.Remove,
|
||||
Flags: ui.Flags(),
|
||||
},
|
||||
{
|
||||
Name: "info",
|
||||
Usage: "Show UI plugin information",
|
||||
Action: ui.Info,
|
||||
Flags: ui.InfoFlags(),
|
||||
},
|
||||
{
|
||||
Name: "version",
|
||||
Usage: "Show VIC version information",
|
||||
Action: showVersion,
|
||||
},
|
||||
}
|
||||
if Version != "" {
|
||||
app.Version = fmt.Sprintf("%s-%s-%s", Version, BuildID, CommitID)
|
||||
} else {
|
||||
app.Version = fmt.Sprintf("%s-%s", BuildID, CommitID)
|
||||
}
|
||||
|
||||
logs := []io.Writer{app.Writer}
|
||||
// Open log file
|
||||
// #nosec: Expect file permissions to be 0600 or less
|
||||
f, err := os.OpenFile(LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
// #nosec: Errors unhandled.
|
||||
fmt.Fprintf(os.Stderr, "Error opening logfile %s: %v\n", LogFile, err)
|
||||
} else {
|
||||
defer f.Close()
|
||||
logs = append(logs, f)
|
||||
}
|
||||
|
||||
// Initiliaze logger with default TextFormatter
|
||||
log.SetFormatter(viclog.NewTextFormatter())
|
||||
// SetOutput to io.MultiWriter so that we can log to stdout and a file
|
||||
log.SetOutput(io.MultiWriter(logs...))
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Errorf("--------------------")
|
||||
log.Errorf("%s failed: %s\n", app.Name, errors.ErrorStack(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func showVersion(cli *cli.Context) error {
|
||||
// #nosec: Errors unhandled.
|
||||
fmt.Fprintf(cli.App.Writer, "%v version %v\n", cli.App.Name, cli.App.Version)
|
||||
return nil
|
||||
}
|
||||
417
vendor/github.com/vmware/vic/cmd/vic-ui/ui/ui.go
generated
vendored
Normal file
417
vendor/github.com/vmware/vic/cmd/vic-ui/ui/ui.go
generated
vendored
Normal file
@@ -0,0 +1,417 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/vmware/vic/cmd/vic-machine/common"
|
||||
"github.com/vmware/vic/lib/install/ova"
|
||||
"github.com/vmware/vic/lib/install/plugin"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
// Plugin has all input parameters for vic-ui ui command
|
||||
type Plugin struct {
|
||||
*common.Target
|
||||
common.Debug
|
||||
|
||||
Force bool
|
||||
Configure bool
|
||||
Insecure bool
|
||||
|
||||
Company string
|
||||
HideInSolutionManager bool
|
||||
Key string
|
||||
Name string
|
||||
ServerThumbprint string
|
||||
Summary string
|
||||
Type string
|
||||
URL string
|
||||
Version string
|
||||
EntityType string
|
||||
}
|
||||
|
||||
func NewUI() *Plugin {
|
||||
p := &Plugin{Target: common.NewTarget()}
|
||||
return p
|
||||
}
|
||||
|
||||
// Flags return all cli flags for ui
|
||||
func (p *Plugin) Flags() []cli.Flag {
|
||||
flags := []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "Force install",
|
||||
Destination: &p.Force,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "configure-ova",
|
||||
Usage: "Configure OVA ManagedBy information with plugin",
|
||||
Destination: &p.Configure,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "company",
|
||||
Value: "",
|
||||
Usage: "Plugin company name (required)",
|
||||
Destination: &p.Company,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "key",
|
||||
Value: "",
|
||||
Usage: "Plugin key (required)",
|
||||
Destination: &p.Key,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "name",
|
||||
Value: "",
|
||||
Usage: "Plugin name (required)",
|
||||
Destination: &p.Name,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-show",
|
||||
Usage: "Hide plugin in UI",
|
||||
Destination: &p.HideInSolutionManager,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "server-thumbprint",
|
||||
Value: "",
|
||||
Usage: "Plugin server thumbprint (required for HTTPS plugin URL)",
|
||||
Destination: &p.ServerThumbprint,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "summary",
|
||||
Value: "",
|
||||
Usage: "Plugin summary (required)",
|
||||
Destination: &p.Summary,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "url",
|
||||
Value: "",
|
||||
Usage: "Plugin URL (required)",
|
||||
Destination: &p.URL,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "version",
|
||||
Value: "",
|
||||
Usage: "Plugin version (required)",
|
||||
Destination: &p.Version,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "type",
|
||||
Value: "",
|
||||
Usage: "Managed entity type",
|
||||
Destination: &p.EntityType,
|
||||
},
|
||||
}
|
||||
flags = append(p.TargetFlags(), flags...)
|
||||
flags = append(flags, p.DebugFlags(true)...)
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
// InfoFlags return info command
|
||||
func (p *Plugin) InfoFlags() []cli.Flag {
|
||||
flags := []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "key",
|
||||
Value: "",
|
||||
Usage: "Plugin key (required)",
|
||||
Destination: &p.Key,
|
||||
},
|
||||
}
|
||||
flags = append(p.TargetFlags(), flags...)
|
||||
flags = append(flags, p.DebugFlags(true)...)
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func (p *Plugin) processInstallParams(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if err := p.HasCredentials(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Company == "" {
|
||||
return cli.NewExitError("--company must be specified", 1)
|
||||
}
|
||||
|
||||
if p.Key == "" {
|
||||
return cli.NewExitError("--key must be specified", 1)
|
||||
}
|
||||
|
||||
if p.Name == "" {
|
||||
return cli.NewExitError("--name must be specified", 1)
|
||||
}
|
||||
|
||||
if p.Summary == "" {
|
||||
return cli.NewExitError("--summary must be specified", 1)
|
||||
}
|
||||
|
||||
if p.URL == "" {
|
||||
return cli.NewExitError("--url must be specified", 1)
|
||||
}
|
||||
|
||||
if p.Version == "" {
|
||||
return cli.NewExitError("--version must be specified", 1)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(p.URL), "https://") && p.ServerThumbprint == "" {
|
||||
return cli.NewExitError("--server-thumbprint must be specified when using HTTPS plugin URL", 1)
|
||||
}
|
||||
|
||||
p.Insecure = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) processRemoveParams(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if err := p.HasCredentials(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Key == "" {
|
||||
return cli.NewExitError("--key must be specified", 1)
|
||||
}
|
||||
|
||||
p.Insecure = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) processInfoParams(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin("", op))
|
||||
|
||||
if err := p.HasCredentials(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Key == "" {
|
||||
return cli.NewExitError("--key must be specified", 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) Install(cli *cli.Context) error {
|
||||
op := trace.NewOperation(context.Background(), "Install")
|
||||
|
||||
var err error
|
||||
if err = p.processInstallParams(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Debug.Debug != nil && *p.Debug.Debug > 0 {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
trace.Logger.Level = log.DebugLevel
|
||||
}
|
||||
|
||||
if len(cli.Args()) > 0 {
|
||||
log.Error("Install cannot continue: invalid CLI arguments")
|
||||
log.Errorf("Unknown argument: %s", cli.Args()[0])
|
||||
return errors.New("invalid CLI arguments")
|
||||
}
|
||||
|
||||
log.Infof("### Installing UI Plugin ####")
|
||||
|
||||
pInfo := &plugin.Info{
|
||||
Company: p.Company,
|
||||
Key: p.Key,
|
||||
Name: p.Name,
|
||||
ServerThumbprint: p.ServerThumbprint,
|
||||
ShowInSolutionManager: !p.HideInSolutionManager,
|
||||
Summary: p.Summary,
|
||||
Type: "vsphere-client-serenity",
|
||||
URL: p.URL,
|
||||
Version: p.Version,
|
||||
}
|
||||
|
||||
if p.EntityType != "" {
|
||||
pInfo.ManagedEntityInfo = &plugin.ManagedEntityInfo{
|
||||
Description: p.Summary,
|
||||
EntityType: p.EntityType,
|
||||
}
|
||||
}
|
||||
|
||||
pl, err := plugin.NewPluginator(context.TODO(), p.Target.URL, p.Target.Thumbprint, pInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reg, err := pl.IsRegistered(pInfo.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reg {
|
||||
if p.Force {
|
||||
log.Info("Removing existing plugin to force install")
|
||||
err = pl.Unregister(pInfo.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Removed existing plugin")
|
||||
} else {
|
||||
msg := fmt.Sprintf("plugin (%s) is already registered", pInfo.Key)
|
||||
log.Errorf("Install failed: %s", msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Installing plugin")
|
||||
err = pl.Register()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reg, err = pl.IsRegistered(pInfo.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !reg {
|
||||
msg := fmt.Sprintf("post-install check failed to find %s registered", pInfo.Key)
|
||||
log.Errorf("Install failed: %s", msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
log.Info("Installed UI plugin")
|
||||
|
||||
if p.Configure {
|
||||
sessionConfig := &session.Config{
|
||||
Service: p.Target.URL.Scheme + "://" + p.Target.URL.Host,
|
||||
User: p.Target.URL.User,
|
||||
Thumbprint: p.Thumbprint,
|
||||
Insecure: true,
|
||||
}
|
||||
|
||||
// Configure the OVA vm to be managed by this plugin
|
||||
if err = ova.ConfigureManagedByInfo(context.TODO(), sessionConfig, pInfo.URL); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) Remove(cli *cli.Context) error {
|
||||
op := trace.NewOperation(context.Background(), "Remove")
|
||||
|
||||
var err error
|
||||
if err = p.processRemoveParams(op); err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Debug.Debug != nil && *p.Debug.Debug > 0 {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
trace.Logger.Level = log.DebugLevel
|
||||
}
|
||||
|
||||
if len(cli.Args()) > 0 {
|
||||
log.Error("Remove cannot continue: invalid CLI arguments")
|
||||
log.Errorf("Unknown argument: %s", cli.Args()[0])
|
||||
return errors.New("invalid CLI arguments")
|
||||
}
|
||||
|
||||
if p.Force {
|
||||
log.Info("Ignoring --force")
|
||||
}
|
||||
|
||||
log.Infof("### Removing UI Plugin ####")
|
||||
|
||||
pInfo := &plugin.Info{
|
||||
Key: p.Key,
|
||||
}
|
||||
|
||||
pl, err := plugin.NewPluginator(context.TODO(), p.Target.URL, p.Target.Thumbprint, pInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reg, err := pl.IsRegistered(pInfo.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reg {
|
||||
log.Infof("Found target plugin: %s", pInfo.Key)
|
||||
} else {
|
||||
msg := fmt.Sprintf("failed to find target plugin (%s)", pInfo.Key)
|
||||
log.Errorf("Remove failed: %s", msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
log.Info("Removing plugin")
|
||||
err = pl.Unregister(pInfo.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reg, err = pl.IsRegistered(pInfo.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reg {
|
||||
msg := fmt.Sprintf("post-remove check found %s still registered", pInfo.Key)
|
||||
log.Errorf("Remove failed: %s", msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
log.Info("Removed UI plugin")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) Info(cli *cli.Context) error {
|
||||
op := trace.NewOperation(context.Background(), "Info")
|
||||
|
||||
var err error
|
||||
if err = p.processInfoParams(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(cli.Args()) > 0 {
|
||||
log.Error("Info cannot continue: invalid CLI arguments")
|
||||
log.Errorf("Unknown argument: %s", cli.Args()[0])
|
||||
return errors.New("invalid CLI arguments")
|
||||
}
|
||||
|
||||
pInfo := &plugin.Info{
|
||||
Key: p.Key,
|
||||
}
|
||||
|
||||
pl, err := plugin.NewPluginator(context.TODO(), p.Target.URL, p.Target.Thumbprint, pInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reg, err := pl.GetPlugin(p.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reg == nil {
|
||||
return errors.Errorf("%s is not registered", p.Key)
|
||||
}
|
||||
|
||||
log.Infof("%s is registered", p.Key)
|
||||
log.Info("")
|
||||
log.Infof("Key: %s", reg.Key)
|
||||
log.Infof("Name: %s", reg.Description.GetDescription().Label)
|
||||
log.Infof("Summary: %s", reg.Description.GetDescription().Summary)
|
||||
log.Infof("Company: %s", reg.Company)
|
||||
log.Infof("Version: %s", reg.Version)
|
||||
return nil
|
||||
}
|
||||
1
vendor/github.com/vmware/vic/cmd/vicadmin/html
generated
vendored
Symbolic link
1
vendor/github.com/vmware/vic/cmd/vicadmin/html
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../../isos/vicadmin
|
||||
530
vendor/github.com/vmware/vic/cmd/vicadmin/logs.go
generated
vendored
Normal file
530
vendor/github.com/vmware/vic/cmd/vicadmin/logs.go
generated
vendored
Normal file
@@ -0,0 +1,530 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/hpcloud/tail"
|
||||
|
||||
"path/filepath"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/lib/pprof"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
const (
|
||||
nBytes = 1024
|
||||
tailLines = 8
|
||||
uint32max = (1 << 32) - 1
|
||||
|
||||
// how many lines of log data to collect
|
||||
logLines = 5000
|
||||
// how many lines to request per call
|
||||
lines = 500
|
||||
)
|
||||
|
||||
type dlogReader struct {
|
||||
c *session.Session
|
||||
name string
|
||||
host *object.HostSystem
|
||||
}
|
||||
|
||||
func (r dlogReader) open() (entry, error) {
|
||||
defer trace.End(trace.Begin(r.name))
|
||||
|
||||
name := r.name
|
||||
if r.host != nil {
|
||||
name = fmt.Sprintf("%s-%s", path.Base(r.host.InventoryPath), r.name)
|
||||
}
|
||||
|
||||
m := object.NewDiagnosticManager(r.c.Vim25())
|
||||
ctx := context.Background()
|
||||
|
||||
// Currently we collect the tail of diagnostic log files to avoid
|
||||
// reading the entire file into memory or writing local disk.
|
||||
|
||||
// get LineEnd without any LineText
|
||||
h, err := m.BrowseLog(ctx, r.host, r.name, math.MaxInt32, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
end := h.LineEnd
|
||||
start := end - logLines
|
||||
|
||||
var buf bytes.Buffer
|
||||
for start < end {
|
||||
h, err = m.BrowseLog(ctx, r.host, r.name, start, lines)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, line := range h.LineText {
|
||||
buf.WriteString(line)
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
|
||||
start += lines
|
||||
}
|
||||
|
||||
return newBytesEntry(name+".log", buf.Bytes()), nil
|
||||
}
|
||||
|
||||
// logFiles has a potential race condition since logrotation can rotate files this moment of time.
|
||||
// however, the likely hood of this race is so low that it doesn't worth investing the time to do
|
||||
// cross process synchronization.
|
||||
func logFiles() []string {
|
||||
defer trace.End(trace.Begin(""))
|
||||
files, err := ioutil.ReadDir(logFileDir)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get a list of log files: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
names := []string{}
|
||||
for _, fileInfo := range files {
|
||||
if fileInfo.IsDir() {
|
||||
continue
|
||||
}
|
||||
fname := fileInfo.Name()
|
||||
log.Debugf("Found potential file for export: %s", fname)
|
||||
|
||||
for _, f := range logFileListPrefixes {
|
||||
if strings.HasPrefix(fname, f) {
|
||||
fp := filepath.Join(logFileDir, fname)
|
||||
log.Debugf("Adding file for export: %s", fp)
|
||||
names = append(names, fp)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// logFile writes the contents of file f and any of it's rotated files to the http writer.
|
||||
// includeF is used for tailing, in which case we want to write rotated files but not f itself
|
||||
func writeLogFiles(w http.ResponseWriter, r *http.Request, f string, includeF bool) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
files, err := ioutil.ReadDir(logFileDir)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get a list of log files: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// find all rotated files, but not f itself
|
||||
names := []string{}
|
||||
for _, fileInfo := range files {
|
||||
fname := fileInfo.Name()
|
||||
if fileInfo.IsDir() || (fname == f) {
|
||||
continue
|
||||
}
|
||||
log.Debugf("Found potential file for export: %s", fname)
|
||||
|
||||
if strings.HasPrefix(fname, f) {
|
||||
fp := filepath.Join(logFileDir, fname)
|
||||
names = append(names, fp)
|
||||
}
|
||||
}
|
||||
// sort file names to preserve time rotation order
|
||||
sort.Strings(names)
|
||||
|
||||
// find f last and append it to names
|
||||
for _, fileInfo := range files {
|
||||
fname := fileInfo.Name()
|
||||
if fileInfo.IsDir() {
|
||||
continue
|
||||
}
|
||||
if fname == f {
|
||||
log.Debugf("Found potential file for export: %s", fname)
|
||||
fp := filepath.Join(logFileDir, fname)
|
||||
names = append(names, fp)
|
||||
}
|
||||
}
|
||||
|
||||
if len(names) == 0 {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
||||
// write file contents to w
|
||||
for _, fileName := range names {
|
||||
file, err := os.Open(fileName)
|
||||
log.Debugf("Writing contents of: %s", fileName)
|
||||
if err != nil {
|
||||
log.Errorf("error opening file %s: %s", fileName, err.Error())
|
||||
continue
|
||||
}
|
||||
// using interface type here so we can reassign r as a gzip reader for rotated logs
|
||||
var r io.ReadCloser = file
|
||||
if strings.HasSuffix(fileName, "gz") {
|
||||
r, err = gzip.NewReader(file)
|
||||
if err != nil {
|
||||
log.Errorf("error opening gzipped file %s: %s", fileName, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
_, err = io.Copy(w, r)
|
||||
if err != nil {
|
||||
log.Errorf("error writing contents of %s: %s", fileName, err.Error())
|
||||
continue
|
||||
}
|
||||
r.Close()
|
||||
file.Close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func configureReaders() map[string]entryReader {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
pprofPaths := map[string]string{
|
||||
// verbose
|
||||
"verbose": "/debug/pprof/goroutine?debug=2",
|
||||
// concise
|
||||
"concise": "/debug/pprof/goroutine?debug=1",
|
||||
"block": "/debug/pprof/block?debug=1",
|
||||
"heap": "/debug/pprof/heap?debug=1",
|
||||
"profile": "/debug/pprof/profile",
|
||||
}
|
||||
|
||||
pprofSources := map[string]string{
|
||||
"docker": pprof.GetPprofEndpoint(pprof.DockerPort).String(),
|
||||
"portlayer": pprof.GetPprofEndpoint(pprof.PortlayerPort).String(),
|
||||
"vicadm": pprof.GetPprofEndpoint(pprof.VicadminPort).String(),
|
||||
"vic-init": pprof.GetPprofEndpoint(pprof.VCHInitPort).String(),
|
||||
}
|
||||
|
||||
readers := map[string]entryReader{
|
||||
"proc-mounts": fileReader("/proc/mounts"),
|
||||
"uptime": commandReader("uptime"),
|
||||
"df": commandReader("df"),
|
||||
"free": commandReader("free"),
|
||||
"netstat": commandReader("netstat -ant"),
|
||||
"iptables": commandReader("sudo iptables --list"),
|
||||
"ip-link": commandReader("ip link"),
|
||||
"ip-addr": commandReader("ip addr"),
|
||||
"ip-route": commandReader("ip route"),
|
||||
"lsmod": commandReader("lsmod"),
|
||||
// TODO: ls without shelling out
|
||||
"disk-by-path": commandReader("ls -l /dev/disk/by-path"),
|
||||
"disk-by-label": commandReader("ls -l /dev/disk/by-label"),
|
||||
"disk-by-uuid": commandReader("ls -l /dev/disk/by-uuid"),
|
||||
"lsblk": commandReader("lsblk -S"),
|
||||
// To check we are not leaking any fds
|
||||
"proc-self-fd": commandReader("ls -l /proc/self/fd"),
|
||||
"ps": commandReader("ps -ef"),
|
||||
"meminfo": fileReader("/proc/meminfo"),
|
||||
"journalctl": commandReader("/bin/journalctl --no-pager"),
|
||||
"dmesg": commandReader("/bin/journalctl --dmesg --no-pager"),
|
||||
"sys-block": commandReader("ls -l /sys/block/"),
|
||||
// To check version
|
||||
"VERSION": versionReader("version"),
|
||||
}
|
||||
|
||||
// add the pprof collection
|
||||
for sname, source := range pprofSources {
|
||||
for pname, paths := range pprofPaths {
|
||||
rname := fmt.Sprintf("%s/%s", sname, pname)
|
||||
readers[rname] = urlReader(source + paths)
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range logFiles() {
|
||||
// Strip off leading '/'
|
||||
readers[path[1:]] = fileReader(path)
|
||||
}
|
||||
|
||||
return readers
|
||||
}
|
||||
|
||||
func findDiagnosticLogs(c *session.Session) (map[string]entryReader, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// When connected to VC, we collect vpxd.log and hostd.log for all cluster hosts attached to the datastore.
|
||||
// When connected to ESX, we just collect hostd.log.
|
||||
const (
|
||||
vpxdKey = "vpxd:vpxd.log"
|
||||
hostdKey = "hostd"
|
||||
)
|
||||
|
||||
logs := map[string]entryReader{}
|
||||
var err error
|
||||
|
||||
if c.IsVC() {
|
||||
logs[vpxdKey] = dlogReader{c, vpxdKey, nil}
|
||||
|
||||
var hosts []*object.HostSystem
|
||||
if c.Cluster == nil && c.Host != nil {
|
||||
hosts = []*object.HostSystem{c.Host}
|
||||
} else {
|
||||
hosts, err = c.Datastore.AttachedClusterHosts(context.TODO(), c.Cluster)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
lname := fmt.Sprintf("%s/%s", hostdKey, host)
|
||||
logs[lname] = dlogReader{c, hostdKey, host}
|
||||
}
|
||||
} else {
|
||||
logs[hostdKey] = dlogReader{c, hostdKey, nil}
|
||||
}
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func tarEntries(readers map[string]entryReader, out io.Writer) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
r, w := io.Pipe()
|
||||
t := tar.NewWriter(w)
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(1)
|
||||
|
||||
// stream tar to out
|
||||
go func() {
|
||||
_, err := io.Copy(out, r)
|
||||
if err != nil {
|
||||
log.Errorf("error copying tar: %s", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
for name, r := range readers {
|
||||
log.Infof("Collecting log with reader %s(%#v)", name, r)
|
||||
|
||||
e, err := r.open()
|
||||
if err != nil {
|
||||
log.Warningf("error reading %s(%s): %s\n", name, r, err)
|
||||
continue
|
||||
}
|
||||
var sz int64
|
||||
if e != nil {
|
||||
sz = e.Size()
|
||||
}
|
||||
|
||||
header := tar.Header{
|
||||
Name: name,
|
||||
Size: sz,
|
||||
Mode: 0640,
|
||||
ModTime: time.Now(),
|
||||
}
|
||||
|
||||
err = t.WriteHeader(&header)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to write header for %s: %s", header.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("%s has size %d", header.Name, header.Size)
|
||||
|
||||
// be explicit about the number of bytes to copy as the log files will likely
|
||||
// be written to during this exercise
|
||||
if e != nil {
|
||||
_, err = io.CopyN(t, e, sz)
|
||||
// #nosec: Errors unhandled.
|
||||
_ = e.Close()
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Failed to write content for %s: %s", header.Name, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
_ = t.Flush()
|
||||
// #nosec: Errors unhandled.
|
||||
_ = w.Close()
|
||||
wg.Wait()
|
||||
// #nosec: Errors unhandled.
|
||||
_ = r.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func zipEntries(readers map[string]entryReader, out *zip.Writer) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
defer out.Close()
|
||||
defer out.Flush()
|
||||
|
||||
for name, r := range readers {
|
||||
log.Infof("Collecting log with reader %s(%#v)", name, r)
|
||||
|
||||
e, err := r.open()
|
||||
if err != nil {
|
||||
log.Warningf("error reading %s(%s): %s\n", name, r, err)
|
||||
}
|
||||
var sz int64
|
||||
if e != nil {
|
||||
sz = e.Size()
|
||||
}
|
||||
header := &zip.FileHeader{
|
||||
Name: name,
|
||||
Method: zip.Deflate,
|
||||
}
|
||||
|
||||
header.SetModTime(time.Now())
|
||||
header.SetMode(0644)
|
||||
if sz > uint32max {
|
||||
header.UncompressedSize = uint32max
|
||||
} else {
|
||||
header.UncompressedSize = uint32(sz)
|
||||
}
|
||||
|
||||
w, err := out.CreateHeader(header)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create Zip writer for %s: %s", header.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("%s has size %d", header.Name, sz)
|
||||
|
||||
// be explicit about the number of bytes to copy as the log files will likely
|
||||
// be written to during this exercise
|
||||
if e != nil {
|
||||
_, err = io.CopyN(w, e, sz)
|
||||
// #nosec: Errors unhandled.
|
||||
_ = e.Close()
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Failed to write content for %s: %s", header.Name, err)
|
||||
continue
|
||||
}
|
||||
log.Infof("Wrote %d bytes to %s", sz, header.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func tailFile(wr io.Writer, file string, done *chan bool) error {
|
||||
defer trace.End(trace.Begin(file))
|
||||
|
||||
// By default, seek to EOF (if file doesn't exist)
|
||||
spos := tail.SeekInfo{
|
||||
Offset: 0,
|
||||
Whence: 2,
|
||||
}
|
||||
|
||||
// If the file exists, we want to go back tailLines lines
|
||||
// and pass that new offset into the TailFile() constructor
|
||||
// Per @fdawg4l, use bytes.LastIndex() and a 1k buffer to reduce
|
||||
// seeks/reads
|
||||
f, err := os.Open(file)
|
||||
if err == nil {
|
||||
spos = tail.SeekInfo{
|
||||
Offset: findSeekPos(f),
|
||||
Whence: 0,
|
||||
}
|
||||
}
|
||||
|
||||
tcfg := tail.Config{
|
||||
Location: &spos,
|
||||
ReOpen: true,
|
||||
MustExist: false,
|
||||
Follow: true,
|
||||
}
|
||||
|
||||
t, err := tail.TailFile(file, tcfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We KNOW there's a data race here.
|
||||
// But it doesn't break anything, so we just trap it.
|
||||
defer func() {
|
||||
t.Stop()
|
||||
_ = recover()
|
||||
}()
|
||||
for true {
|
||||
select {
|
||||
case l := <-t.Lines:
|
||||
if l.Err != nil {
|
||||
return l.Err
|
||||
}
|
||||
fmt.Fprint(wr, l.Text, "\n")
|
||||
case _ = <-*done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find the offset we want to start tailing from.
|
||||
// This should either be beginning-of-file or tailLines
|
||||
// newlines from the EOF.
|
||||
func findSeekPos(f *os.File) int64 {
|
||||
defer trace.End(trace.Begin(""))
|
||||
nlines := tailLines
|
||||
readPos, err := f.Seek(0, 2)
|
||||
// If for some reason we can't seek, we will just start tailing from beginning-of-file
|
||||
if err != nil {
|
||||
return int64(0)
|
||||
}
|
||||
|
||||
// Buffer so we can seek nBytes (default: 1k) at a time
|
||||
buf := make([]byte, nBytes)
|
||||
|
||||
for readPos > 0 {
|
||||
// Go back nBytes from the last readPos we've seen (stopping at beginning-of-file)
|
||||
// and read the next nBytes
|
||||
readPos -= int64(len(buf))
|
||||
if readPos < 0 {
|
||||
// We don't want to overlap our read with previous reads...
|
||||
buf = buf[:(int(readPos) + nBytes)]
|
||||
readPos = 0
|
||||
}
|
||||
bufend, err := f.ReadAt(buf, readPos)
|
||||
|
||||
// It's OK to get io.EOF here. Anything else is bad.
|
||||
if err != nil && err != io.EOF {
|
||||
log.Errorf("Error reading from file %s: %s", f.Name(), err)
|
||||
return 0
|
||||
}
|
||||
|
||||
// Start from the end of the buffer and start looking for newlines
|
||||
for bufend > 0 {
|
||||
bufend = bytes.LastIndexByte(buf[:bufend], '\n')
|
||||
if bufend < 0 {
|
||||
break
|
||||
}
|
||||
nlines--
|
||||
if nlines < 0 {
|
||||
return readPos + int64(bufend) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
575
vendor/github.com/vmware/vic/cmd/vicadmin/server.go
generated
vendored
Normal file
575
vendor/github.com/vmware/vic/cmd/vicadmin/server.go
generated
vendored
Normal file
@@ -0,0 +1,575 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"compress/gzip"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/google/uuid"
|
||||
gorillacontext "github.com/gorilla/context"
|
||||
|
||||
"github.com/vmware/vic/lib/vicadmin"
|
||||
"github.com/vmware/vic/pkg/filelock"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
l net.Listener
|
||||
addr string
|
||||
mux *http.ServeMux
|
||||
uss *UserSessionStore
|
||||
}
|
||||
|
||||
// LoginPageData contains items needed to render the login page template
|
||||
type LoginPageData struct {
|
||||
Hostname string
|
||||
SystemTime string
|
||||
InvalidLogin string
|
||||
}
|
||||
|
||||
type format int
|
||||
|
||||
const (
|
||||
formatTGZ format = iota
|
||||
formatZip
|
||||
)
|
||||
|
||||
var beginningOfTime = time.Unix(0, 0).Format(time.RFC3339)
|
||||
|
||||
const (
|
||||
sessionExpiration = time.Hour * 24
|
||||
sessionCookieKey = "session-data"
|
||||
sessionCreationTimeKey = "created"
|
||||
sessionKey = "session-id"
|
||||
ipAddressKey = "ip"
|
||||
loginPagePath = "/authentication"
|
||||
authFailure = loginPagePath + "?unauthorized"
|
||||
genericErrorMessage = "Internal Server Error; see /var/log/vic/vicadmin.log for details" // for http errors that shouldn't be displayed in the browser to the user
|
||||
)
|
||||
|
||||
func (s *server) listen() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var err error
|
||||
var certificate *tls.Certificate
|
||||
s.uss = NewUserSessionStore()
|
||||
if vchConfig.HostCertificate != nil {
|
||||
certificate, err = vchConfig.HostCertificate.Certificate()
|
||||
} else {
|
||||
var c tls.Certificate
|
||||
if c, err = tls.X509KeyPair(
|
||||
rootConfig.serverCert.Cert.Bytes(),
|
||||
rootConfig.serverCert.Key.Bytes()); err != nil {
|
||||
log.Errorf("Could not generate self-signed certificate for vicadmin running with due to error %s", err.Error())
|
||||
return err
|
||||
}
|
||||
certificate = &c
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Could not load certificate from config - running without TLS: %s", err)
|
||||
|
||||
s.l, err = net.Listen("tcp", s.addr)
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME: assignment copies lock value to tlsConfig: crypto/tls.Config contains sync.Once contains sync.Mutex
|
||||
tlsconfig := func(c *tls.Config) *tls.Config {
|
||||
// if there are CAs, then TLS is enabled
|
||||
if len(vchConfig.CertificateAuthorities) != 0 {
|
||||
if c.ClientCAs == nil {
|
||||
c.ClientCAs = x509.NewCertPool()
|
||||
}
|
||||
if !c.ClientCAs.AppendCertsFromPEM(vchConfig.CertificateAuthorities) {
|
||||
log.Errorf("Unable to load CAs from config; client auth via certificate will not function")
|
||||
}
|
||||
c.ClientAuth = tls.VerifyClientCertIfGiven
|
||||
} else {
|
||||
log.Warnf("No certificate authorities found for certificate-based authentication. This may be intentional, however, certificate-based authentication is disabled")
|
||||
}
|
||||
|
||||
// #nosec: TLS InsecureSkipVerify may be true
|
||||
return &tls.Config{
|
||||
Certificates: c.Certificates,
|
||||
NameToCertificate: c.NameToCertificate,
|
||||
GetCertificate: c.GetCertificate,
|
||||
RootCAs: c.RootCAs,
|
||||
NextProtos: c.NextProtos,
|
||||
ServerName: c.ServerName,
|
||||
ClientAuth: c.ClientAuth,
|
||||
ClientCAs: c.ClientCAs,
|
||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||
CipherSuites: c.CipherSuites,
|
||||
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
||||
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
||||
SessionTicketKey: c.SessionTicketKey,
|
||||
ClientSessionCache: c.ClientSessionCache,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: c.MaxVersion,
|
||||
CurvePreferences: c.CurvePreferences,
|
||||
}
|
||||
}(tlsconfig.ServerDefault())
|
||||
|
||||
tlsconfig.Certificates = []tls.Certificate{*certificate}
|
||||
|
||||
innerListener, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
s.l = tls.NewListener(innerListener, tlsconfig)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) listenPort() int {
|
||||
return s.l.Addr().(*net.TCPAddr).Port
|
||||
}
|
||||
|
||||
// Enforces authentication on route `link` and runs `handler` on successful auth
|
||||
func (s *server) AuthenticatedHandle(link string, h http.Handler) {
|
||||
s.Authenticated(link, h.ServeHTTP)
|
||||
}
|
||||
|
||||
func (s *server) Handle(link string, h http.Handler) {
|
||||
log.Debugf("%s --- %s", time.Now().String(), link)
|
||||
s.mux.Handle(link, gorillacontext.ClearHandler(h))
|
||||
}
|
||||
|
||||
// Enforces authentication on route `link` and runs `handler` on successful auth
|
||||
func (s *server) Authenticated(link string, handler func(http.ResponseWriter, *http.Request)) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
authHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
// #nosec: Errors unhandled because it is okay if the cookie doesn't exist.
|
||||
websession, _ := s.uss.cookies.Get(r, sessionCookieKey)
|
||||
|
||||
if len(r.TLS.PeerCertificates) > 0 {
|
||||
// the user is authenticated by certificate at connection time
|
||||
log.Infof("Authenticated connection via client certificate with serial %s from %s", r.TLS.PeerCertificates[0].SerialNumber, r.RemoteAddr)
|
||||
key := uuid.New().String()
|
||||
|
||||
vs, err := vSphereSessionGet(&rootConfig.Config)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get vSphere session with default config for cert-auth'd user")
|
||||
http.Error(w, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
usersess := s.uss.Add(key, &rootConfig.Config, vs)
|
||||
|
||||
timeNow, err := usersess.created.MarshalText()
|
||||
if err != nil {
|
||||
// it's probably safe to ignore this error since we just created usersess.created when we called Add() above
|
||||
// but just in case..
|
||||
log.Errorf("Failed to unmarshal time object %+v into text due to error: %s", usersess.created, err)
|
||||
http.Error(w, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
websession.Values[sessionCreationTimeKey] = string(timeNow)
|
||||
websession.Values[sessionKey] = key
|
||||
|
||||
remoteAddr := strings.SplitN(r.RemoteAddr, ":", 2)
|
||||
if len(remoteAddr) != 2 { // TODO: ctrl+f RemoteAddr and move this routine to helper
|
||||
log.Errorf("Format of IP address %s (should be IP:PORT) not recognized", r.RemoteAddr)
|
||||
http.Error(w, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
websession.Values[ipAddressKey] = remoteAddr[0]
|
||||
err = websession.Save(r, w)
|
||||
if err != nil {
|
||||
log.Errorf("Could not create session for user authenticated via client certificate due to error \"%s\"", err.Error())
|
||||
http.Error(w, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// user was authenticated via cert
|
||||
handler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
c := websession.Values[sessionCreationTimeKey]
|
||||
if c == nil { // no cookie, so redirect to login
|
||||
log.Infof("No authentication token: %+v", websession.Values)
|
||||
http.Redirect(w, r, loginPagePath, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
// here we have a cookie, but we need to make sure it's not expired:
|
||||
// parse the cookie creation time
|
||||
created, err := time.Parse(time.RFC3339, c.(string))
|
||||
if err != nil {
|
||||
// we pulled this value out of a cookie, so if it doesn't parse, it might've been tampered with
|
||||
// though the cookie's encrypted so that would destroy the whole cookie..
|
||||
// Handling the error in any case:
|
||||
log.Errorf("Couldn't parse time out of retrieved cookie due to error %s", err.Error())
|
||||
http.Error(w, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// cookie exists but is expired
|
||||
if time.Since(created) > sessionExpiration {
|
||||
http.Redirect(w, r, loginPagePath, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
// verify that the auth token is being used by the same IP it was created for
|
||||
c = websession.Values[ipAddressKey]
|
||||
if c == nil {
|
||||
log.Errorf("Couldn't get IP address out of cookie for user connecting from %s at %s", r.RemoteAddr, time.Now())
|
||||
http.Redirect(w, r, loginPagePath, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
connectingAddr := strings.SplitN(r.RemoteAddr, ":", 2)
|
||||
if len(connectingAddr) != 2 { // TODO: ctrl+f r.RemoteAddr and move this routine to helper
|
||||
log.Errorf("Format of IP address %s (should be IP:PORT) not recognized", r.RemoteAddr)
|
||||
http.Error(w, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if c.(string) != connectingAddr[0] {
|
||||
log.Warnf("User with a valid auth cookie from %s has reappeared at %s. Their token will be expired.", c.(string), connectingAddr[0])
|
||||
s.logoutHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// if the date & remote IP on the cookie were valid, then the user is authenticated
|
||||
log.Infof("User with a valid auth cookie at %s is authenticated.", connectingAddr[0])
|
||||
handler(w, r)
|
||||
}
|
||||
s.Handle(link, http.HandlerFunc(authHandler))
|
||||
}
|
||||
|
||||
// renders the page for login and handles authorization requests
|
||||
func (s *server) loginPage(res http.ResponseWriter, req *http.Request) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
var invalidLoginMessage = ""
|
||||
|
||||
if req.Method == "POST" {
|
||||
// take the form data and use it to try to authenticate with vsphere
|
||||
|
||||
// create a userconfig
|
||||
userconfig := session.Config{
|
||||
Insecure: false,
|
||||
Thumbprint: rootConfig.Thumbprint,
|
||||
Keepalive: rootConfig.Keepalive,
|
||||
ClusterPath: rootConfig.ClusterPath,
|
||||
DatacenterPath: rootConfig.DatacenterPath,
|
||||
DatastorePath: rootConfig.DatastorePath,
|
||||
HostPath: rootConfig.Config.HostPath,
|
||||
PoolPath: rootConfig.PoolPath,
|
||||
User: url.UserPassword(req.FormValue("username"), req.FormValue("password")),
|
||||
Service: rootConfig.Service,
|
||||
}
|
||||
|
||||
// check login
|
||||
vs, err := vSphereSessionGet(&userconfig)
|
||||
if err != nil || vs == nil {
|
||||
// something went wrong or we could not authenticate
|
||||
log.Warnf("%s failed to authenticate ", req.RemoteAddr)
|
||||
invalidLoginMessage = "Authentication failed due to incorrect credential(s)"
|
||||
hostName, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostName = "VCH"
|
||||
}
|
||||
loginPageData := &LoginPageData{
|
||||
Hostname: hostName,
|
||||
SystemTime: time.Now().Format(time.UnixDate),
|
||||
InvalidLogin: invalidLoginMessage,
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFiles("auth.html")
|
||||
err = tmpl.ExecuteTemplate(res, "auth.html", loginPageData)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing template: %s", err)
|
||||
http.Error(res, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// successful login above; user is authenticated, reported for audit purposes
|
||||
log.Debugf("User %s from %s was successfully authenticated", userconfig.User.Username(), req.RemoteAddr)
|
||||
|
||||
// create a token to save as an encrypted & signed cookie
|
||||
websession, err := s.uss.cookies.Get(req, sessionCookieKey)
|
||||
if websession == nil {
|
||||
log.Errorf("Web session object could not be created due to error %s", err)
|
||||
http.Error(res, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
key := uuid.New().String()
|
||||
userconfig.User = nil
|
||||
userconfig.Service = ""
|
||||
us := s.uss.Add(key, &userconfig, vs)
|
||||
|
||||
timeNow, err := us.created.MarshalText()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to unmarshal time object %+v into text due to error: %s", us.created, err)
|
||||
http.Error(res, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
websession.Values[sessionCreationTimeKey] = string(timeNow)
|
||||
websession.Values[sessionKey] = key
|
||||
|
||||
remoteAddr := strings.SplitN(req.RemoteAddr, ":", 2)
|
||||
if len(remoteAddr) != 2 { // TODO: ctrl+f RemoteAddr and move this routine to helper
|
||||
log.Errorf("Format of IP address %s (should be IP:PORT) not recognized", req.RemoteAddr)
|
||||
http.Error(res, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
websession.Values[ipAddressKey] = remoteAddr[0]
|
||||
|
||||
if err := websession.Save(req, res); err != nil {
|
||||
log.Errorf("\"%s\" occurred while trying to save session to browser", err.Error())
|
||||
http.Error(res, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// redirect to dashboard
|
||||
http.Redirect(res, req, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
// Render login page (shows up on non-POST requests)
|
||||
hostName, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostName = "VCH"
|
||||
}
|
||||
loginPageData := &LoginPageData{
|
||||
Hostname: hostName,
|
||||
SystemTime: time.Now().Format(time.UnixDate),
|
||||
InvalidLogin: invalidLoginMessage,
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFiles("auth.html")
|
||||
err = tmpl.ExecuteTemplate(res, "auth.html", loginPageData)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing template: %s", err)
|
||||
http.Error(res, genericErrorMessage, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) serve() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
s.mux = http.NewServeMux()
|
||||
|
||||
// unauthenticated routes
|
||||
// these assets bypass authentication & are world-readable
|
||||
s.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("css/"))))
|
||||
s.Handle("/images/", http.StripPrefix("/images/", http.FileServer(http.Dir("images/"))))
|
||||
s.Handle("/fonts/", http.StripPrefix("/fonts/", http.FileServer(http.Dir("fonts/"))))
|
||||
|
||||
s.Handle(loginPagePath, http.HandlerFunc(s.loginPage))
|
||||
|
||||
// authenticated routes
|
||||
// tar of appliance system logs
|
||||
s.Authenticated("/logs.tar.gz", s.tarDefaultLogs)
|
||||
s.Authenticated("/logs.zip", s.zipDefaultLogs)
|
||||
|
||||
// tar of appliance system logs + container logs
|
||||
s.Authenticated("/container-logs.tar.gz", s.tarContainerLogs)
|
||||
s.Authenticated("/container-logs.zip", s.zipContainerLogs)
|
||||
|
||||
// get single log file (no tail)
|
||||
s.Authenticated("/logs/", func(w http.ResponseWriter, r *http.Request) {
|
||||
file := strings.TrimPrefix(r.URL.Path, "/logs/")
|
||||
log.Debugf("writing contents for %s", file)
|
||||
writeLogFiles(w, r, file, true)
|
||||
})
|
||||
|
||||
// get single log file (with tail)
|
||||
s.Authenticated("/logs/tail/", func(w http.ResponseWriter, r *http.Request) {
|
||||
file := strings.TrimPrefix(r.URL.Path, "/logs/tail/")
|
||||
log.Debugf("writing contents for %s", file)
|
||||
writeLogFiles(w, r, file, false)
|
||||
s.tailFiles(w, r, []string{filepath.Join(logFileDir, file)})
|
||||
})
|
||||
|
||||
s.Authenticated("/logout", s.logoutHandler)
|
||||
s.Authenticated("/", s.index)
|
||||
server := &http.Server{
|
||||
Handler: s.mux,
|
||||
}
|
||||
|
||||
return server.Serve(s.l)
|
||||
}
|
||||
|
||||
func (s *server) stop() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if s.l != nil {
|
||||
err := s.l.Close()
|
||||
s.l = nil
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// logout handler expires the user's session cookie by setting its creation time to the beginning of time
|
||||
func (s *server) logoutHandler(res http.ResponseWriter, req *http.Request) {
|
||||
// #nosec: Errors unhandled.
|
||||
websession, _ := s.uss.cookies.Get(req, sessionCookieKey)
|
||||
// ignore parsing/marshalling errors because we're parsing a hardcoded beginning-of-time string
|
||||
websession.Values[sessionCreationTimeKey] = beginningOfTime
|
||||
if err := websession.Save(req, res); err != nil {
|
||||
http.Error(res, "Failed to expire user session", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
s.uss.Delete(websession.Values[sessionKey].(string))
|
||||
http.Redirect(res, req, "/authentication", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (s *server) bundleContainerLogs(res http.ResponseWriter, req *http.Request, f format) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
logrotateLock := filelock.NewFileLock(filelock.LogRotateLockName)
|
||||
if err := logrotateLock.Acquire(); err != nil {
|
||||
log.Errorf("Failed to acquire logrotate lock: %s", err)
|
||||
} else {
|
||||
defer func() { logrotateLock.Release() }()
|
||||
}
|
||||
|
||||
readers := configureReaders()
|
||||
c, err := s.getSessionFromRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get vSphere session while bundling container logs due to error: %s", err.Error())
|
||||
http.Redirect(res, req, "/logout", http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
logs, err := findDatastoreLogs(c)
|
||||
if err != nil {
|
||||
log.Warningf("error searching datastore: %s", err)
|
||||
} else {
|
||||
for key, rdr := range logs {
|
||||
readers[key] = rdr
|
||||
}
|
||||
}
|
||||
|
||||
logs, err = findDiagnosticLogs(c)
|
||||
if err != nil {
|
||||
log.Warningf("error collecting diagnostic logs: %s", err)
|
||||
} else {
|
||||
for key, rdr := range logs {
|
||||
readers[key] = rdr
|
||||
}
|
||||
}
|
||||
|
||||
s.bundleLogs(res, req, readers, f)
|
||||
}
|
||||
|
||||
func (s *server) tarDefaultLogs(res http.ResponseWriter, req *http.Request) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
s.bundleLogs(res, req, configureReaders(), formatTGZ)
|
||||
}
|
||||
|
||||
func (s *server) zipDefaultLogs(res http.ResponseWriter, req *http.Request) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
s.bundleLogs(res, req, configureReaders(), formatZip)
|
||||
}
|
||||
|
||||
func (s *server) bundleLogs(res http.ResponseWriter, req *http.Request, readers map[string]entryReader, f format) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var err error
|
||||
if f == formatTGZ {
|
||||
res.Header().Set("Content-Type", "application/x-gzip")
|
||||
z := gzip.NewWriter(res)
|
||||
defer z.Close()
|
||||
err = tarEntries(readers, z)
|
||||
} else if f == formatZip {
|
||||
res.Header().Set("Content-Type", "application/zip")
|
||||
z := zip.NewWriter(res)
|
||||
defer z.Close()
|
||||
err = zipEntries(readers, z)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error bundling logs: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) tarContainerLogs(res http.ResponseWriter, req *http.Request) {
|
||||
s.bundleContainerLogs(res, req, formatTGZ)
|
||||
}
|
||||
|
||||
func (s *server) zipContainerLogs(res http.ResponseWriter, req *http.Request) {
|
||||
s.bundleContainerLogs(res, req, formatZip)
|
||||
}
|
||||
|
||||
func (s *server) tailFiles(res http.ResponseWriter, req *http.Request, names []string) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
cc := res.(http.CloseNotifier).CloseNotify()
|
||||
|
||||
fw := &flushWriter{
|
||||
f: res.(http.Flusher),
|
||||
w: res,
|
||||
}
|
||||
|
||||
done := make(chan bool)
|
||||
for _, file := range names {
|
||||
go tailFile(fw, file, &done)
|
||||
}
|
||||
|
||||
<-cc
|
||||
for range names {
|
||||
done <- true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) index(res http.ResponseWriter, req *http.Request) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
ctx := context.Background()
|
||||
sess, err := s.getSessionFromRequest(ctx, req)
|
||||
if err != nil {
|
||||
log.Errorf("While loading index page got %s looking up a vSphere session", err.Error())
|
||||
http.Redirect(res, req, "/logout", http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
v := vicadmin.NewValidator(ctx, &vchConfig, sess)
|
||||
if sess == nil {
|
||||
// We're unable to connect to vSphere, so display an error message
|
||||
v.VCHIssues = template.HTML("<span class=\"error-message\">We're having some trouble communicating with vSphere. <a href=\"/logout\">Logging in again</a> may resolve the issue.</span>\n")
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFiles("dashboard.html")
|
||||
err = tmpl.ExecuteTemplate(res, "dashboard.html", v)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing template: %s", err)
|
||||
}
|
||||
}
|
||||
133
vendor/github.com/vmware/vic/cmd/vicadmin/usersession.go
generated
vendored
Normal file
133
vendor/github.com/vmware/vic/cmd/vicadmin/usersession.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/gorilla/sessions"
|
||||
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
// UserSession holds a user's session metadata
|
||||
type UserSession struct {
|
||||
id string
|
||||
created time.Time
|
||||
config *session.Config
|
||||
vsphere *session.Session
|
||||
}
|
||||
|
||||
// UserSessionStore holds and manages user sessions
|
||||
type UserSessionStore struct {
|
||||
mutex sync.RWMutex
|
||||
sessions map[string]*UserSession
|
||||
ticker *time.Ticker
|
||||
cookies *sessions.CookieStore
|
||||
}
|
||||
|
||||
type UserSessionStorer interface {
|
||||
Add(id string, config *session.Config, vs *session.Session) *UserSession
|
||||
Delete(id string)
|
||||
VSphere(id string) (vSphereSession *session.Session, err error)
|
||||
UserSession(id string) *UserSession
|
||||
}
|
||||
|
||||
// Add a session. VS may be nil if host is plain ESX
|
||||
func (u *UserSessionStore) Add(id string, config *session.Config, vs *session.Session) *UserSession {
|
||||
u.mutex.Lock()
|
||||
defer u.mutex.Unlock()
|
||||
sess := &UserSession{
|
||||
id: id,
|
||||
created: time.Now(),
|
||||
// TODO strip out config cause it's not needed anymore, but shows up in a number of places
|
||||
config: config,
|
||||
vsphere: vs,
|
||||
}
|
||||
u.sessions[id] = sess
|
||||
return sess
|
||||
}
|
||||
|
||||
func (u *UserSessionStore) Delete(id string) {
|
||||
u.mutex.Lock()
|
||||
defer u.mutex.Unlock()
|
||||
delete(u.sessions, id)
|
||||
}
|
||||
|
||||
// Grabs the UserSession metadata object and doesn't establish a connection to vSphere
|
||||
func (u *UserSessionStore) UserSession(id string) *UserSession {
|
||||
u.mutex.RLock()
|
||||
defer u.mutex.RUnlock()
|
||||
return u.sessions[id]
|
||||
}
|
||||
|
||||
// Returns a vSphere session object. Caller responsible for error handling/logout
|
||||
func (u *UserSessionStore) VSphere(ctx context.Context, id string) (*session.Session, error) {
|
||||
us := u.UserSession(id)
|
||||
if us == nil {
|
||||
return nil, fmt.Errorf("User session with unique ID %s does not exist", id)
|
||||
}
|
||||
if us.vsphere == nil {
|
||||
return nil, fmt.Errorf("No vSphere session found for user: %s", id)
|
||||
}
|
||||
|
||||
vsphus, err := us.vsphere.SessionManager.UserSession(ctx)
|
||||
if err != nil || vsphus == nil {
|
||||
if err != nil {
|
||||
log.Warnf("Failed to validate user %s session: %v", id, err)
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("User %s session has expired", id)
|
||||
}
|
||||
log.Infof("Found vSphere session for vicadmin usersession %s", id)
|
||||
return us.vsphere, nil
|
||||
}
|
||||
|
||||
// reaper takes abandoned sessions to a farm upstate so they don't build up forever
|
||||
func (u *UserSessionStore) reaper() {
|
||||
for range u.ticker.C {
|
||||
for id, session := range u.sessions {
|
||||
if time.Since(session.created) > sessionExpiration {
|
||||
u.Delete(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewUserSessionStore creates & initializes a UserSessionStore and starts a session reaper in the background
|
||||
func NewUserSessionStore() *UserSessionStore {
|
||||
u := &UserSessionStore{
|
||||
sessions: make(map[string]*UserSession),
|
||||
ticker: time.NewTicker(time.Minute * 10),
|
||||
mutex: sync.RWMutex{},
|
||||
cookies: sessions.NewCookieStore(
|
||||
[]byte(securecookie.GenerateRandomKey(64)),
|
||||
[]byte(securecookie.GenerateRandomKey(32))),
|
||||
}
|
||||
u.cookies.Options = &sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: int(sessionExpiration.Seconds()),
|
||||
Secure: true,
|
||||
HttpOnly: true,
|
||||
}
|
||||
go u.reaper()
|
||||
return u
|
||||
}
|
||||
634
vendor/github.com/vmware/vic/cmd/vicadmin/vicadm.go
generated
vendored
Normal file
634
vendor/github.com/vmware/vic/cmd/vicadmin/vicadm.go
generated
vendored
Normal file
@@ -0,0 +1,634 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/govmomi"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
vchconfig "github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/lib/guest"
|
||||
"github.com/vmware/vic/lib/pprof"
|
||||
"github.com/vmware/vic/pkg/certificate"
|
||||
viclog "github.com/vmware/vic/pkg/log"
|
||||
"github.com/vmware/vic/pkg/log/syslog"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/compute"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
const (
|
||||
timeout = time.Duration(2 * time.Second)
|
||||
)
|
||||
|
||||
type serverCertificate struct {
|
||||
Key bytes.Buffer
|
||||
Cert bytes.Buffer
|
||||
}
|
||||
|
||||
type vicAdminConfig struct {
|
||||
session.Config
|
||||
addr string
|
||||
tls bool
|
||||
serverCert *serverCertificate
|
||||
}
|
||||
|
||||
var (
|
||||
logFileDir = "/var/log/vic"
|
||||
logFileListPrefixes = []string{
|
||||
"docker-personality.log",
|
||||
"port-layer.log",
|
||||
"vicadmin.log",
|
||||
"init.log",
|
||||
"kubelet-starter.log",
|
||||
"virtual-kubelet.log",
|
||||
}
|
||||
|
||||
// VMFiles is the set of files to collect per VM associated with the VCH
|
||||
vmFiles = []string{
|
||||
"output.log",
|
||||
"vmware.log",
|
||||
"tether.debug",
|
||||
}
|
||||
|
||||
// this struct holds root credentials or vSphere extension private key instead if available
|
||||
// if you are exposing log information to a user, create a new session for that user, do not use this one
|
||||
// also, 'root' is a pun -- this is both the "root" config, e.g., the base config, and the one w/ root creds
|
||||
rootConfig vicAdminConfig
|
||||
|
||||
resources vchconfig.Resources
|
||||
|
||||
vchConfig vchconfig.VirtualContainerHostConfigSpec
|
||||
|
||||
datastore types.ManagedObjectReference
|
||||
)
|
||||
|
||||
type logfile struct {
|
||||
URL url.URL
|
||||
VMName string
|
||||
Host *object.HostSystem
|
||||
}
|
||||
|
||||
func Init() {
|
||||
// #nosec: Errors unhandled.
|
||||
_ = pprof.StartPprof("vicadmin", pprof.VicadminPort)
|
||||
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// load the vch config
|
||||
src, err := extraconfig.GuestInfoSource()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to load configuration from guestinfo")
|
||||
return
|
||||
}
|
||||
|
||||
extraconfig.Decode(src, &vchConfig)
|
||||
|
||||
logcfg := viclog.NewLoggingConfig()
|
||||
if vchConfig.Diagnostics.DebugLevel > 0 {
|
||||
logcfg.Level = log.DebugLevel
|
||||
trace.Logger.Level = log.DebugLevel
|
||||
syslog.Logger.Level = log.DebugLevel
|
||||
}
|
||||
|
||||
if vchConfig.Diagnostics.SysLogConfig != nil {
|
||||
logcfg.Syslog = &viclog.SyslogConfig{
|
||||
Network: vchConfig.Diagnostics.SysLogConfig.Network,
|
||||
RAddr: vchConfig.Diagnostics.SysLogConfig.RAddr,
|
||||
Priority: syslog.Info | syslog.Daemon,
|
||||
}
|
||||
}
|
||||
|
||||
viclog.Init(logcfg)
|
||||
trace.InitLogger(logcfg)
|
||||
|
||||
// We don't want to run this as root.
|
||||
ud := syscall.Getuid()
|
||||
gd := syscall.Getgid()
|
||||
log.Info(fmt.Sprintf("Current UID/GID = %d/%d", ud, gd))
|
||||
// TODO: Enable this after we figure out to NOT break the test suite with it.
|
||||
// if ud == 0 {
|
||||
// log.Errorf("Error: vicadmin must not run as root.")
|
||||
// time.Sleep(60 * time.Second)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
|
||||
flag.StringVar(&rootConfig.addr, "l", "client.localhost:2378", "Listen address")
|
||||
|
||||
// TODO: This should all be pulled from the config
|
||||
flag.StringVar(&rootConfig.DatacenterPath, "dc", "", "Path of the datacenter")
|
||||
flag.StringVar(&rootConfig.ClusterPath, "cluster", "", "Path of the cluster")
|
||||
flag.StringVar(&rootConfig.PoolPath, "pool", "", "Path of the resource pool")
|
||||
|
||||
if vchConfig.HostCertificate == nil {
|
||||
log.Infoln("--no-tls is enabled on the personality")
|
||||
rootConfig.serverCert = &serverCertificate{}
|
||||
rootConfig.serverCert.Cert, rootConfig.serverCert.Key, err = certificate.CreateSelfSigned(rootConfig.addr, []string{"VMware, Inc."}, 2048)
|
||||
if err != nil {
|
||||
log.Errorf("--no-tls was specified but we couldn't generate a self-signed cert for vic admin due to error %s so vicadmin will not run", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: pull the rest from flags
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
type entryReader interface {
|
||||
open() (entry, error)
|
||||
}
|
||||
|
||||
type entry interface {
|
||||
io.ReadCloser
|
||||
Name() string
|
||||
Size() int64
|
||||
}
|
||||
|
||||
type bytesEntry struct {
|
||||
io.ReadCloser
|
||||
name string
|
||||
size int64
|
||||
}
|
||||
|
||||
func (e *bytesEntry) Name() string {
|
||||
return e.name
|
||||
}
|
||||
|
||||
func (e *bytesEntry) Size() int64 {
|
||||
return e.size
|
||||
}
|
||||
|
||||
func newBytesEntry(name string, b []byte) entry {
|
||||
r := bytes.NewReader(b)
|
||||
|
||||
return &bytesEntry{
|
||||
ReadCloser: ioutil.NopCloser(r),
|
||||
size: int64(r.Len()),
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
type versionReader string
|
||||
|
||||
func (path versionReader) open() (entry, error) {
|
||||
defer trace.End(trace.Begin(string(path)))
|
||||
return newBytesEntry(string(path), []byte(version.GetBuild().ShortVersion())), nil
|
||||
}
|
||||
|
||||
type commandReader string
|
||||
|
||||
func (path commandReader) open() (entry, error) {
|
||||
defer trace.End(trace.Begin(string(path)))
|
||||
|
||||
args := strings.Split(string(path), " ")
|
||||
// #nosec: Subprocess launching with variable
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %s", err, string(output))
|
||||
}
|
||||
|
||||
return newBytesEntry(string(path), output), nil
|
||||
}
|
||||
|
||||
type fileReader string
|
||||
|
||||
type fileEntry struct {
|
||||
io.ReadCloser
|
||||
os.FileInfo
|
||||
}
|
||||
|
||||
func (path fileReader) open() (entry, error) {
|
||||
defer trace.End(trace.Begin(string(path)))
|
||||
|
||||
f, err := os.Open(string(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s, err := os.Stat(string(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Files in /proc always have struct stat.st_size==0, so just read it into memory.
|
||||
if s.Size() == 0 && strings.HasPrefix(f.Name(), "/proc/") {
|
||||
b, err := ioutil.ReadAll(f)
|
||||
// #nosec: Errors unhandled.
|
||||
_ = f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newBytesEntry(f.Name(), b), nil
|
||||
}
|
||||
|
||||
return &fileEntry{
|
||||
ReadCloser: f,
|
||||
FileInfo: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type urlReader string
|
||||
|
||||
func httpEntry(name string, res *http.Response) (entry, error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(res.Status)
|
||||
}
|
||||
|
||||
if res.ContentLength > 0 {
|
||||
return &bytesEntry{
|
||||
ReadCloser: res.Body,
|
||||
size: res.ContentLength,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// If we don't have Content-Length, read into memory for the tar.Header.Size
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
// #nosec: Errors unhandled.
|
||||
_ = res.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newBytesEntry(name, body), nil
|
||||
}
|
||||
|
||||
func (path urlReader) open() (entry, error) {
|
||||
defer trace.End(trace.Begin(string(path)))
|
||||
client := http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
res, err := client.Get(string(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return httpEntry(string(path), res)
|
||||
}
|
||||
|
||||
type datastoreReader struct {
|
||||
ds *object.Datastore
|
||||
path string
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// listVMPaths returns an array of datastore paths for VMs associated with the
|
||||
// VCH - this includes containerVMs and the appliance
|
||||
func listVMPaths(ctx context.Context, s *session.Session) ([]logfile, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var err error
|
||||
var children []*vm.VirtualMachine
|
||||
|
||||
if len(vchConfig.ComputeResources) == 0 {
|
||||
return nil, errors.New("compute resources is empty")
|
||||
}
|
||||
|
||||
ref := vchConfig.ComputeResources[0]
|
||||
rp := compute.NewResourcePool(ctx, s, ref)
|
||||
if children, err = rp.GetChildrenVMs(ctx, s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
self, err := guest.GetSelf(ctx, s)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get handle to self for log filtering")
|
||||
}
|
||||
|
||||
log.Infof("Found %d candidate VMs in resource pool %s for log collection", len(children), ref.String())
|
||||
|
||||
logfiles := []logfile{}
|
||||
for _, child := range children {
|
||||
path, err := child.VMPathNameAsURL(ctx)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get datastore path for child VM %s: %s", child.Reference(), err)
|
||||
// we need to get as many logs as possible
|
||||
continue
|
||||
}
|
||||
|
||||
logname, err := child.Name(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get the vm name for %s: %s", child.Reference(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if self != nil && child.Reference().String() == self.Reference().String() {
|
||||
// FIXME: until #2630 is addressed, and we confirm this filters secrets from appliance vmware.log as well,
|
||||
// we're skipping direct collection of those logs.
|
||||
log.Info("Skipping collection for appliance VM (moref match)")
|
||||
continue
|
||||
}
|
||||
|
||||
// backup check if we were unable to initialize self for some reason
|
||||
if self == nil && logname == vchConfig.Name {
|
||||
log.Info("Skipping collection for appliance VM (string match)")
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("Adding VM for log collection: %s", path.String())
|
||||
h, err := child.HostSystem(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to get host system for VM %s - will use default host for log collection: %s", logname, err)
|
||||
}
|
||||
|
||||
log := logfile{
|
||||
URL: path,
|
||||
VMName: logname,
|
||||
Host: h,
|
||||
}
|
||||
|
||||
logfiles = append(logfiles, log)
|
||||
}
|
||||
|
||||
log.Infof("Collecting logs from %d VMs", len(logfiles))
|
||||
log.Infof("Found VM paths are : %#v", logfiles)
|
||||
return logfiles, nil
|
||||
}
|
||||
|
||||
// addApplianceLogs whitelists the logs to include for the appliance.
|
||||
// TODO: once we've started encrypting all potentially sensitive data and filtering out guestinfo.ovfEnv
|
||||
// we can resume collection of vmware.log and drop the appliance specific handling
|
||||
func addApplianceLogs(ctx context.Context, s *session.Session, readers map[string]entryReader) error {
|
||||
self, err := guest.GetSelf(ctx, s)
|
||||
if err != nil || self == nil {
|
||||
return fmt.Errorf("Unable to collect appliance logs due to unknown self-reference: %s", err)
|
||||
}
|
||||
|
||||
self2 := vm.NewVirtualMachineFromVM(ctx, s, self)
|
||||
path, err := self2.VMPathNameAsURL(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ds, err := s.Finder.Datastore(ctx, path.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h, err := self2.HostSystem(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to get host system for appliance - will use default host for log collection: %s", err)
|
||||
} else {
|
||||
ctx = ds.HostContext(ctx, h)
|
||||
}
|
||||
|
||||
wpath := fmt.Sprintf("appliance/tether.debug")
|
||||
rpath := fmt.Sprintf("%s/%s", path.Path, "tether.debug")
|
||||
log.Infof("Processed File read Path : %s", rpath)
|
||||
log.Infof("Processed File write Path : %s", wpath)
|
||||
readers[wpath] = datastoreReader{
|
||||
ds: ds,
|
||||
path: rpath,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// find datastore logs for the appliance itself and all containers
|
||||
func findDatastoreLogs(c *session.Session) (map[string]entryReader, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// Create an empty reader as opposed to a nil reader...
|
||||
readers := map[string]entryReader{}
|
||||
ctx := context.Background()
|
||||
|
||||
logfiles, err := listVMPaths(ctx, c)
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("unable to perform datastore log collection due to failure looking up paths: %s", err)
|
||||
log.Error(detail)
|
||||
return nil, errors.New(detail)
|
||||
}
|
||||
|
||||
err = addApplianceLogs(ctx, c, readers)
|
||||
if err != nil {
|
||||
log.Errorf("Issue collecting appliance logs: %s", err)
|
||||
}
|
||||
|
||||
for _, logfile := range logfiles {
|
||||
log.Debugf("Assembling datastore readers for %s", logfile.URL.String())
|
||||
// obtain datastore object
|
||||
ds, err := c.Finder.Datastore(ctx, logfile.URL.Host)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to acquire reference to datastore %s: %s", logfile.URL.Host, err)
|
||||
continue
|
||||
}
|
||||
|
||||
hCtx := ctx
|
||||
if logfile.Host != nil {
|
||||
hCtx = ds.HostContext(ctx, logfile.Host)
|
||||
}
|
||||
|
||||
// generate the full paths to collect
|
||||
for _, file := range vmFiles {
|
||||
wpath := fmt.Sprintf("%s/%s", logfile.VMName, file)
|
||||
rpath := fmt.Sprintf("%s/%s", logfile.URL.Path, file)
|
||||
log.Infof("Processed File read Path : %s", rpath)
|
||||
log.Infof("Processed File write Path : %s", wpath)
|
||||
readers[wpath] = datastoreReader{
|
||||
ds: ds,
|
||||
path: rpath,
|
||||
ctx: hCtx,
|
||||
}
|
||||
|
||||
log.Debugf("Added log file for collection: %s", logfile.URL.String())
|
||||
}
|
||||
}
|
||||
|
||||
return readers, nil
|
||||
}
|
||||
|
||||
func (r datastoreReader) open() (entry, error) {
|
||||
defer trace.End(trace.Begin(r.path))
|
||||
|
||||
u, ticket, err := r.ds.ServiceTicket(r.ctx, r.path, "GET")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if ticket != nil {
|
||||
req.AddCookie(ticket)
|
||||
}
|
||||
|
||||
res, err := r.ds.Client().Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return httpEntry(r.path, res)
|
||||
}
|
||||
|
||||
// stripCredentials removes user credentials from "in"
|
||||
func stripCredentials(in *session.Session) error {
|
||||
serviceURL, err := soap.ParseURL(rootConfig.Service)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing service URL from config: %s", err)
|
||||
return err
|
||||
}
|
||||
serviceURL.User = nil
|
||||
newclient, err := govmomi.NewClient(context.Background(), serviceURL, true)
|
||||
if err != nil {
|
||||
log.Errorf("Error creating new govmomi client without credentials but with auth cookie: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
newclient.Jar = in.Client.Jar
|
||||
in.Client = newclient
|
||||
return nil
|
||||
}
|
||||
|
||||
func vSphereSessionGet(sessconfig *session.Config) (*session.Session, error) {
|
||||
s := session.NewSession(sessconfig)
|
||||
s.UserAgent = version.UserAgent("vic-admin")
|
||||
|
||||
ctx := context.Background()
|
||||
_, err := s.Connect(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to connect: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = s.Populate(ctx)
|
||||
if err != nil {
|
||||
// not a critical error for vicadmin
|
||||
log.Warnf("Unable to populate session: %s", err)
|
||||
}
|
||||
usersession, err := s.SessionManager.UserSession(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Got %s while creating user session", err)
|
||||
return nil, err
|
||||
}
|
||||
if usersession == nil {
|
||||
return nil, fmt.Errorf("vSphere session is no longer valid")
|
||||
}
|
||||
|
||||
log.Infof("Got session from vSphere with key: %s username: %s", usersession.Key, usersession.UserName)
|
||||
|
||||
err = stripCredentials(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *server) getSessionFromRequest(ctx context.Context, r *http.Request) (*session.Session, error) {
|
||||
sessionData, err := s.uss.cookies.Get(r, sessionCookieKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var d interface{}
|
||||
var ok bool
|
||||
if d, ok = sessionData.Values[sessionKey]; !ok {
|
||||
return nil, fmt.Errorf("User-provided cookie did not contain a session ID -- it is corrupt or tampered")
|
||||
}
|
||||
c, err := s.uss.VSphere(ctx, d.(string))
|
||||
return c, err
|
||||
}
|
||||
|
||||
type flushWriter struct {
|
||||
f http.Flusher
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (fw *flushWriter) Write(p []byte) (int, error) {
|
||||
n, err := fw.w.Write(p)
|
||||
|
||||
fw.f.Flush()
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func main() {
|
||||
Init()
|
||||
|
||||
if version.Show() {
|
||||
fmt.Fprintf(os.Stdout, "%s\n", version.String())
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: these should just be consumed directly inside Session
|
||||
rootConfig.Service = vchConfig.Target
|
||||
rootConfig.User = url.UserPassword(vchConfig.Username, vchConfig.Token)
|
||||
rootConfig.Thumbprint = vchConfig.TargetThumbprint
|
||||
rootConfig.DatastorePath = vchConfig.Storage.ImageStores[0].Host
|
||||
|
||||
if vchConfig.Diagnostics.DebugLevel > 0 {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.Info("Setting debug logging")
|
||||
}
|
||||
|
||||
if vchConfig.Diagnostics.DebugLevel > 2 {
|
||||
rootConfig.addr = "0.0.0.0:2378"
|
||||
log.Warn("Listening on all networks because of debug level")
|
||||
}
|
||||
s := &server{
|
||||
addr: rootConfig.addr,
|
||||
}
|
||||
|
||||
err := s.listen()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Infof("listening on %s", s.addr)
|
||||
signals := []syscall.Signal{
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGINT,
|
||||
}
|
||||
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
for _, signum := range signals {
|
||||
signal.Notify(sigchan, signum)
|
||||
}
|
||||
|
||||
go func() {
|
||||
signal := <-sigchan
|
||||
log.Infof("received %s", signal)
|
||||
s.stop()
|
||||
}()
|
||||
|
||||
s.serve()
|
||||
}
|
||||
327
vendor/github.com/vmware/vic/cmd/vicadmin/vicadm_test.go
generated
vendored
Normal file
327
vendor/github.com/vmware/vic/cmd/vicadmin/vicadm_test.go
generated
vendored
Normal file
@@ -0,0 +1,327 @@
|
||||
// Copyright 2016-2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
vchconfig "github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/test/env"
|
||||
)
|
||||
|
||||
// use an http client which we modify in init()
|
||||
// to be permissive with certificates so we can
|
||||
// use a self-signed cert hardcoded into these tests
|
||||
var insecureClient *http.Client
|
||||
|
||||
func init() {
|
||||
// init needs to be updated to include client certificates
|
||||
// so that the disabled tests can be re-enabled
|
||||
sdk := env.URL(nil)
|
||||
if sdk != "" {
|
||||
flag.Set("sdk", sdk)
|
||||
flag.Set("vm-path", "docker-appliance")
|
||||
flag.Set("cluster", os.Getenv("GOVC_CLUSTER"))
|
||||
}
|
||||
|
||||
// fake up a docker-host for pprof collection
|
||||
u := url.URL{Scheme: "http", Host: "127.0.0.1:6060"}
|
||||
|
||||
go func() {
|
||||
log.Println(http.ListenAndServe(u.Host, nil))
|
||||
}()
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
insecureClient = &http.Client{Transport: transport}
|
||||
flag.Set("docker-host", u.Host)
|
||||
|
||||
hostCertFile := "fixtures/vicadmin_test_cert.pem"
|
||||
hostKeyFile := "fixtures/vicadmin_test_pkey.pem"
|
||||
|
||||
cert, cerr := ioutil.ReadFile(hostCertFile)
|
||||
key, kerr := ioutil.ReadFile(hostKeyFile)
|
||||
if kerr != nil || cerr != nil {
|
||||
panic("unable to load test certificate")
|
||||
}
|
||||
vchConfig.HostCertificate = &vchconfig.RawCertificate{
|
||||
Cert: cert,
|
||||
Key: key,
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogFiles(t *testing.T) {
|
||||
logFileNames := []string{}
|
||||
for _, name := range logFiles() {
|
||||
logFileNames = append(logFileNames, name)
|
||||
}
|
||||
fileCount := 0
|
||||
//files should be in same order, otherwise we have evidence of a suspected race
|
||||
for _, name := range logFiles() {
|
||||
assert.Equal(t, name, logFileNames[fileCount])
|
||||
fileCount++
|
||||
}
|
||||
}
|
||||
|
||||
func testLogTar(t *testing.T, plainHTTP bool) {
|
||||
t.SkipNow() // TODO FIXME auth is in place now
|
||||
|
||||
if runtime.GOOS != "linux" {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
logFileDir = "."
|
||||
|
||||
s := &server{
|
||||
addr: "127.0.0.1:0",
|
||||
}
|
||||
|
||||
err := s.listen()
|
||||
assert.NoError(t, err)
|
||||
|
||||
port := s.listenPort()
|
||||
|
||||
go s.serve()
|
||||
defer s.stop()
|
||||
|
||||
var res *http.Response
|
||||
res, err = insecureClient.Get(fmt.Sprintf("https://root:thisisinsecure@localhost:%d/container-logs.tar.gz", port))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
z, err := gzip.NewReader(res.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tz := tar.NewReader(z)
|
||||
|
||||
for {
|
||||
h, err := tz.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
name, err := url.QueryUnescape(h.Name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if testing.Verbose() {
|
||||
fmt.Printf("\n%s...\n", name)
|
||||
io.CopyN(os.Stdout, tz, 150)
|
||||
fmt.Printf("...\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogTar(t *testing.T) {
|
||||
t.SkipNow() // TODO FIXME auth is in place now
|
||||
testLogTar(t, false)
|
||||
testLogTar(t, true)
|
||||
}
|
||||
|
||||
func TestLogTail(t *testing.T) {
|
||||
t.SkipNow() // TODO FIXME auth is in place now
|
||||
if runtime.GOOS != "linux" {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
f, err := os.OpenFile("./vicadmin.log", os.O_RDWR|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
f.WriteString("# not much here yet\n")
|
||||
|
||||
logFileDir = "."
|
||||
name := filepath.Base(f.Name())
|
||||
|
||||
s := &server{
|
||||
addr: "127.0.0.1:0",
|
||||
// auth: &credentials{"root", "thisisinsecure"},
|
||||
}
|
||||
|
||||
err = s.listen()
|
||||
assert.NoError(t, err)
|
||||
|
||||
port := s.listenPort()
|
||||
|
||||
go s.serve()
|
||||
defer s.stop()
|
||||
|
||||
out := ioutil.Discard
|
||||
if testing.Verbose() {
|
||||
out = os.Stdout
|
||||
}
|
||||
|
||||
paths := []string{
|
||||
"/logs/tail/" + name,
|
||||
}
|
||||
|
||||
u := url.URL{
|
||||
// User: url.UserPassword("root", "thisisinsecure"),
|
||||
Scheme: "https",
|
||||
Host: fmt.Sprintf("localhost:%d", port),
|
||||
}
|
||||
|
||||
str := "The quick brown fox jumps over the lazy dog.\n"
|
||||
|
||||
// Pre-populate the log file
|
||||
for i := 0; i < tailLines; i++ {
|
||||
f.WriteString(str)
|
||||
}
|
||||
|
||||
log.Printf("Testing TestLogTail\n")
|
||||
for _, path := range paths {
|
||||
u.Path = path
|
||||
log.Printf("GET %s:\n", u.String())
|
||||
res, err := insecureClient.Get(u.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
// Each line written to the log file has enough bytes to ensure
|
||||
// that 8 lines make up at least 256 bytes. This is in case this
|
||||
// goroutine finishes writing all lines before tail kicks in.
|
||||
go func() {
|
||||
for j := 1; j < 512; j++ {
|
||||
f.WriteString(str)
|
||||
}
|
||||
f.Sync()
|
||||
}()
|
||||
|
||||
size := int64(256)
|
||||
n, err := io.CopyN(out, res.Body, size)
|
||||
assert.NoError(t, err)
|
||||
out.Write([]byte("...\n"))
|
||||
|
||||
assert.Equal(t, size, n)
|
||||
}
|
||||
}
|
||||
|
||||
type seekTest struct {
|
||||
input string
|
||||
output int
|
||||
}
|
||||
|
||||
func testSeek(t *testing.T, st seekTest, td string) {
|
||||
f, err := ioutil.TempFile(td, "FindSeekPos")
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
log.Printf("Unable to create temporary file: %s", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
n, err := f.WriteString(st.input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != len(st.input) {
|
||||
t.Fatal(fmt.Errorf("Incorrect byte count on write: %d/%d", n, len(st.input)))
|
||||
}
|
||||
|
||||
if ret := findSeekPos(f); ret != int64(st.output) {
|
||||
t.Fatal(fmt.Errorf("Incorrect seek position: %d/%d", ret, st.output))
|
||||
}
|
||||
log.Printf("Successfully seeked to position %d", st.output)
|
||||
os.Remove(f.Name())
|
||||
}
|
||||
func TestFindSeekPos(t *testing.T) {
|
||||
if runtime.GOOS != "linux" {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
seekTests := []seekTest{
|
||||
{"abcd\nabcd\n", 0},
|
||||
{"abcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\n", 10},
|
||||
}
|
||||
|
||||
str := "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
||||
str = fmt.Sprintf("%s%s", str, str)
|
||||
str2 := fmt.Sprintf("%s\n%s\n%s\n", str, str, str)
|
||||
str2 = fmt.Sprintf("%s%s", str2, str2)
|
||||
// Verify we don't have overlapping reads at beginning of file
|
||||
// 6 lines, 1206 characters. Should come back with seek position 0
|
||||
seekTests = append(seekTests, seekTest{str2, 0})
|
||||
|
||||
for seg := 0; seg < 3; seg++ {
|
||||
str = str + str
|
||||
}
|
||||
str2 = str + "\n"
|
||||
fmt.Printf("str length is %d\n", len(str))
|
||||
// str is 1,601 chars long now
|
||||
for line := 0; line < 2; line++ {
|
||||
str2 = str2 + str2
|
||||
}
|
||||
|
||||
// str2 is 4 lines long. Should seek to beginning of file
|
||||
seekTests = append(seekTests, seekTest{str2, 0})
|
||||
|
||||
// str2 is now 12 lines long. Should seek to position 6,404
|
||||
str2 = fmt.Sprintf("%s%s%s", str2, str2, str2)
|
||||
seekTests = append(seekTests, seekTest{str2, 6404})
|
||||
|
||||
td := os.TempDir()
|
||||
for i, st := range seekTests {
|
||||
log.Printf("Test case #%d: ", i)
|
||||
testSeek(t, st, td)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionReaderOpen(t *testing.T) {
|
||||
version.Version = "1.2.1"
|
||||
version.BuildNumber = "12345"
|
||||
version.GitCommit = "abcdefg"
|
||||
|
||||
var vReader versionReader
|
||||
en, err := vReader.open()
|
||||
assert.NoError(t, err)
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(en)
|
||||
fullVersion := buf.String()
|
||||
versionFields := strings.SplitN(fullVersion, "-", 3)
|
||||
assert.Equal(t, len(versionFields), 3)
|
||||
assert.Equal(t, versionFields[0], version.Version)
|
||||
assert.Equal(t, versionFields[1], version.BuildNumber)
|
||||
assert.Equal(t, versionFields[2], version.GitCommit)
|
||||
}
|
||||
37
vendor/github.com/vmware/vic/demos/compose/voting-app/docker-compose.yml
generated
vendored
Normal file
37
vendor/github.com/vmware/vic/demos/compose/voting-app/docker-compose.yml
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
vote:
|
||||
image: victest/vote
|
||||
container_name: vote
|
||||
command: python app.py
|
||||
ports:
|
||||
- "5000:80"
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
container_name: redis
|
||||
ports: ["6379"]
|
||||
|
||||
worker:
|
||||
container_name: worker
|
||||
image: victest/vote-worker
|
||||
|
||||
# Postgres container will create an anonymous volume. VIC will use a
|
||||
# vmdk for the volume. Since it is a new volume, it will have a lost+found
|
||||
# folder, and the Postgres init scripts do not like a non-empty folder (the
|
||||
# lost+found folder). We work around this by directing the init scripts to
|
||||
# use another arbitrary folder. We do this by setting the PGDATA env var.
|
||||
db:
|
||||
container_name: db
|
||||
image: postgres:9.4
|
||||
environment:
|
||||
- PGDATA=/var/lib/postgresql/data/data
|
||||
|
||||
result:
|
||||
image: victest/vote-result
|
||||
container_name: result
|
||||
command: nodemon --debug server.js
|
||||
ports:
|
||||
- "5001:80"
|
||||
- "5858:5858"
|
||||
69
vendor/github.com/vmware/vic/demos/compose/voting-app/votingapp.dab
generated
vendored
Normal file
69
vendor/github.com/vmware/vic/demos/compose/voting-app/votingapp.dab
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"Services": {
|
||||
"db": {
|
||||
"Env": [
|
||||
"PGDATA=/var/lib/postgresql/data/data"
|
||||
],
|
||||
"Image": "postgres@sha256:9db811348585075eddb7b6938fa65c0236fe8e4f7feaf3c2890a3c4b7f4c9bfc",
|
||||
"Networks": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
"redis": {
|
||||
"Image": "redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d",
|
||||
"Networks": [
|
||||
"default"
|
||||
],
|
||||
"Ports": [
|
||||
{
|
||||
"Port": 6379,
|
||||
"Protocol": "tcp"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"Args": [
|
||||
"nodemon",
|
||||
"--debug",
|
||||
"server.js"
|
||||
],
|
||||
"Image": "victest/vote-result@sha256:413fcc5d11e4426d9e5ec512056aeeb7fa83acc61eec2ed6346e8d30d56b7ae7",
|
||||
"Networks": [
|
||||
"default"
|
||||
],
|
||||
"Ports": [
|
||||
{
|
||||
"Port": 80,
|
||||
"Protocol": "tcp"
|
||||
},
|
||||
{
|
||||
"Port": 5858,
|
||||
"Protocol": "tcp"
|
||||
}
|
||||
]
|
||||
},
|
||||
"vote": {
|
||||
"Args": [
|
||||
"python",
|
||||
"app.py"
|
||||
],
|
||||
"Image": "victest/vote@sha256:13037f3373a3b10dfeac4f323b38a215d9665d88d72b1bb5fe7baef83c3b34ad",
|
||||
"Networks": [
|
||||
"default"
|
||||
],
|
||||
"Ports": [
|
||||
{
|
||||
"Port": 80,
|
||||
"Protocol": "tcp"
|
||||
}
|
||||
]
|
||||
},
|
||||
"worker": {
|
||||
"Image": "victest/vote-worker@sha256:70cc69d1e91b8c23bbfd7515572c804c8b954c227da0e55e1131c367929f79dd",
|
||||
"Networks": [
|
||||
"default"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Version": "0.1"
|
||||
}
|
||||
30
vendor/github.com/vmware/vic/demos/compose/webserving-app/docker-compose.yml
generated
vendored
Normal file
30
vendor/github.com/vmware/vic/demos/compose/webserving-app/docker-compose.yml
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
version: '3'
|
||||
|
||||
networks:
|
||||
my_net:
|
||||
driver: bridge
|
||||
|
||||
services:
|
||||
web_server:
|
||||
image: victest/web_elgg
|
||||
container_name: web_server
|
||||
networks:
|
||||
- my_net
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
mysql_server:
|
||||
image: victest/web_db
|
||||
container_name: mysql_server
|
||||
command: [bash, -c, "/execute.sh"]
|
||||
networks:
|
||||
- my_net
|
||||
environment:
|
||||
- web_host=192.168.60.130
|
||||
- root_password=root
|
||||
|
||||
memcache_server:
|
||||
image: cloudsuite/web-serving:memcached_server
|
||||
container_name: memcache_server
|
||||
networks:
|
||||
- my_net
|
||||
57
vendor/github.com/vmware/vic/doc/application_workflow/README.md
generated
vendored
Normal file
57
vendor/github.com/vmware/vic/doc/application_workflow/README.md
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
# Container Workflow
|
||||
|
||||
The following are guideline suggestions for those who want to use VIC to develop and deploy a containerized application. These guidelines pertain to VIC 0.7.0. While VIC continues to progress towards VIC 1.0, the current feature set requires some care in creating a containerized application and deploying it. We present these guidelines from a developer and a devops perspective.
|
||||
|
||||
An example workflow is presented here, in the form of a modified voting app, based on [Docker's voting app](https://github.com/docker/example-voting-app).
|
||||
|
||||
## Container Workflow Feature Set
|
||||
|
||||
General feature set (e.g. Docker run, ps, inspect, etc) used at the CLI will not be discussed. Only feature set important for containerizing apps and deploying them are discussed. These include volume and network support. It is also worth mentioning that basic Docker Compose support is available for application deployment.
|
||||
|
||||
#### Currently Available Features
|
||||
|
||||
1. Docker Compose (basic)
|
||||
2. Registry pull from docker hub and private registry
|
||||
3. Named Data Volumes
|
||||
4. Anonymous Data Volumes
|
||||
5. Bridged Networks
|
||||
6. External Networks
|
||||
7. Port Mapping
|
||||
8. Network Links/Alias
|
||||
|
||||
#### Future Features
|
||||
|
||||
Be aware the following feature are not yet available and must be taken into account when containerizing an app and deploying it.
|
||||
|
||||
1. Docker build
|
||||
2. Registry pushing
|
||||
3. Concurrent data volume sharing between containers
|
||||
4. Local host folder mapping to a container volume
|
||||
5. Local host file mapping to a container
|
||||
6. Docker copy files into a container, both running and stopped
|
||||
7. Docker container inspect does not return all container network for a container
|
||||
|
||||
## Workflow Guidelines
|
||||
|
||||
Anything that can be performed with Docker Compose can be performed manually via the Docker CLI and via scripting using the CLI. This makes Compose a good baseline reference and our guidelines will use it for demonstration purposes. Our guideline uses Docker Compose 1.8.1. The list above in the Future Features section puts constraints on what types of containerized application can be deployed on VIC 0.7.0.
|
||||
|
||||
Please note, these guidelines and recommendations exist for the current feature set in VIC 0.7.0. As VIC approaches 1.0, many of these constraints will go away.
|
||||
|
||||
#### Guidelines for Building Container Images
|
||||
The current lack of docker build and registry pushing means users will need to use regular Docker to build a container and to push it to the global hub or your corporate private registry. The example workflow using Docker's voting app will illustrate how to get around this constraint.
|
||||
|
||||
#### Guidelines for Sharing Config
|
||||
VIC 0.7.0 current lack of data volume sharing and docker copy will put constraints on how configuration are provided to a containerized application. An example of configuration is your web server config files. Our recommendation for getting around the current limitation is to pass in configuration via command line arguments or environment variables. Add a script to the container image that ingest the command line argument/environment variable and pass these configuration to the contained application. A benefit of using environment variables to transfer configuration is the containerized app will more closely follow the popular 12-factor app model.
|
||||
|
||||
With no direct support for sharing volumes between containers processes that must share files have the following options:
|
||||
|
||||
1. build them into the same image and run in the same container
|
||||
2. add a script to the container that mounts an NFS share (containers must be on the same network)
|
||||
a. Run container with NFS server sharing a data volume
|
||||
b. Mount NFS share in whichever containers need to share
|
||||
|
||||
TODO: Provide example of both
|
||||
|
||||
## Example Applications
|
||||
|
||||
We have taken Docker's voting app example and used the above guidelines to modify it for use on VIC 0.7.0. Please follow to this [page](voting_app.md) for more information.
|
||||
14
vendor/github.com/vmware/vic/doc/application_workflow/harbor_registry/README.md
generated
vendored
Normal file
14
vendor/github.com/vmware/vic/doc/application_workflow/harbor_registry/README.md
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Using vSphere Integrated Container Engine with VMware's Harbor
|
||||
|
||||
In this example, we will install VMware's Harbor registry and show how to get vSphere Integrated Container Engine (VIC Engine) 0.8.0 working with Harbor. With 0.8.0, the engine does not have an install-time mechanism to set up a self-signed certificate so we will show the manual steps for post-install setup as a workaround. We will not show how to setup Harbor with LDAP. For that, the reader may visit the [Harbor documentation](https://github.com/vmware/harbor/tree/master/docs) site for more information. Since there is a lot of documentation on the Harbor site for various setup, we will focus on setting up Harbor with a self-signed certificate and setting up VIC Engine to work with this Harbor instance.
|
||||
|
||||
## Prerequisite
|
||||
|
||||
The following example requires a vCenter installation.
|
||||
|
||||
Note: Certificate verification requires all machines using certificates are time/date accurate. This can be achieved using several options, suchas, vSphere web client, vSphere thick client for Windows or govc. In the following, we deploy this example on a vCenter where all ESXi hosts in the cluster have been set up with NTP and were sync'd prior to installing VIC Engine or Harbor.
|
||||
|
||||
## Workflows
|
||||
|
||||
1. [Deploy a VCH to use with Harbor](deploy_vch_with_harbor.md)
|
||||
2. [Post-Install Usage](post_install_usage.md)
|
||||
76
vendor/github.com/vmware/vic/doc/application_workflow/harbor_registry/deploy_vch_with_harbor.md
generated
vendored
Normal file
76
vendor/github.com/vmware/vic/doc/application_workflow/harbor_registry/deploy_vch_with_harbor.md
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# Deploying vSphere Integrated Container Engine with Harbor
|
||||
|
||||
## Prerequisite
|
||||
|
||||
Harbor requires 60GB or more free space on your datastore.
|
||||
|
||||
## Workflow
|
||||
|
||||
We will use VIC Engine 0.8.0 and Harbor 0.5.0 for this example. We will use Ubuntu as OS on our user machine.
|
||||
|
||||
If no server certificate and private key are provided during installation, Harbor will self generate these. It will also provide a self-generated CA (certificate authority) certificate if no server certificate and private key are provided during installation. The OVA installation guide for Harbor can be found in the [Harbor docs](https://github.com/vmware/harbor/blob/master/docs/installation_guide_ova.md). Harbor requires both an IP address and FQDN (fully qualified domain name) for the the server. There is also a DHCP install method available for debugging purposes, but it is not a recommended production deployment model.
|
||||
|
||||
We will assume a Harbor instance has been installed without server certificate and private key. We will also assume we have downloaded the CA cert using the Harbor instuctions. The last steps left to get Harbor working with vSphere Integrated Container Engine is to update standard docker with the Harbor CA cert and deploy a new VCH with the CA cert. The instructions are provided below.
|
||||
<br><br>
|
||||
|
||||
## Update the user working machine with the CA.crt for standard docker
|
||||
|
||||
We must update the standard docker on our laptop so it knows of our CA certificate. Docker can look for additional CA certificates outside of the OS's CA bundle folder if we put new CA certificates in the right location, documented [here](https://docs.docker.com/engine/security/certificates/).
|
||||
|
||||
We create the necessary folder, copy our CA cert file there, and restart docker. This should be all that is necessary. We take the additional steps to verify that we can log onto our Harbor server.
|
||||
|
||||
```
|
||||
loc@Devbox:~/mycerts$ sudo su
|
||||
[sudo] password for loc:
|
||||
root@Devbox:/home/loc/mycerts# mkdir -p /etc/docker/certs.d/<Harbor FQDN>
|
||||
root@Devbox:/home/loc/mycerts# mkdir -p /etc/docker/certs.d/<Harbor IP>
|
||||
root@Devbox:/home/loc/mycerts# cp ca.crt /etc/docker/certs.d/<Harbor FQDN>/
|
||||
root@Devbox:/home/loc/mycerts# cp ca.crt /etc/docker/certs.d/<Harbor IP>/
|
||||
root@Devbox:/home/loc/mycerts# exit
|
||||
exit
|
||||
loc@Devbox:~/mycerts$ sudo systemctl daemon-reload
|
||||
loc@Devbox:~/mycerts$ sudo systemctl restart docker
|
||||
|
||||
loc@Devbox:~$ docker logout <Harbor FQDN>
|
||||
Remove login credentials for <Harbor FQDN>
|
||||
|
||||
loc@Devbox:~$ docker logout <Harbor IP>
|
||||
Remove login credentials for <Harbor IP>
|
||||
|
||||
loc@Devbox:~$ docker login <Harbor FQDN>
|
||||
Username: loc
|
||||
Password:
|
||||
Login Succeeded
|
||||
|
||||
loc@Devbox:~$ docker login <Harbor IP>
|
||||
Username: loc
|
||||
Password:
|
||||
Login Succeeded
|
||||
|
||||
loc@Devbox:~$ docker logout <Harbor FQDN>
|
||||
Remove login credentials for <Harbor FQDN>
|
||||
|
||||
loc@Devbox:~$ docker logout <Harbor IP>
|
||||
Remove login credentials for <Harbor IP>
|
||||
```
|
||||
Notice we create folders for both FQDN and IP in the docker cert folder and copy the CA cert to both. This will allow us to log into the Harbor from Docker using both FQDN and IP address.
|
||||
<br><br>
|
||||
|
||||
## Install a VCH with the new CA certificate
|
||||
|
||||
In this step, we deploy a VCH and specify our CA cert via a --registry-ca parameter in vic-machine. This parameter is a list, meaning we can easily add multiple CA certs by specifying multiple --registry-ca parameters.
|
||||
|
||||
For simplicity, we will install a VCH with the --no-tls flag. This indicates we will not need TLS from a docker CLI to the VCH. This does NOT imply that access to Harbor will be performed without TLS.
|
||||
|
||||
```
|
||||
root@Devbox:/home/loc/go/src/github.com/vmware/vic/bin# ./vic-machine-linux create --target=<vCenter_IP> --image-store="vsanDatastore" --name=vic-docker --user=root -password=<vCenter_password> --compute-resource="/dc1/host/cluster1/Resources" --bridge-network DPortGroup --force --no-tls --registry-ca=ca.crt
|
||||
|
||||
WARN[2016-11-11T11:46:37-08:00] Configuring without TLS - all communications will be insecure
|
||||
|
||||
...
|
||||
|
||||
INFO[2016-11-11T11:47:57-08:00] Installer completed successfully
|
||||
```
|
||||
<br>
|
||||
|
||||
Proceed to [Post-Install Usage](post_install_usage.md) for examples of how to use this deployed VCH with Harbor.
|
||||
72
vendor/github.com/vmware/vic/doc/application_workflow/harbor_registry/post_install_usage.md
generated
vendored
Normal file
72
vendor/github.com/vmware/vic/doc/application_workflow/harbor_registry/post_install_usage.md
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
# Using vSphere Integrated Container Engine with Harbor
|
||||
|
||||
Here we show an example of using a deployed VCH with Harbor as a private registry. We assume that one has been setup using either static IP or FQDN. We also assume standard docker has been updated with the certificate authority cert that can verify the deployed Harbor's server cert.
|
||||
<br><br>
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Develop or obtain a docker container image on a computer (or terminal) using standard docker. Tag the image for Harbor and push the image to the server.
|
||||
2. Pull down the image from Harbor to a deployed VCH and use it.
|
||||
<br><br>
|
||||
|
||||
## Push a container image to Harbor using standard docker
|
||||
|
||||
In this step, we pull the busybox container image from the docker hub down to our laptop, which had the CA certificate updated for docker use earlier. Then we tag the image for uploading to our Harbor registry and push the image up to it. Please note, we log onto the Harbor server before pushing the image up to it.
|
||||
|
||||
```
|
||||
loc@Devbox:~/mycerts$ docker pull busybox
|
||||
Using default tag: latest
|
||||
latest: Pulling from library/busybox
|
||||
|
||||
56bec22e3559: Pull complete
|
||||
Digest: sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912
|
||||
Status: Downloaded newer image for busybox:latest
|
||||
loc@Devbox:~/mycerts$
|
||||
loc@Devbox:~/mycerts$ docker tag busybox <Harbor FQDN or static IP>/test/busybox
|
||||
|
||||
loc@Devbox:~/mycerts$ docker login <Harbor FQDN or static IP>
|
||||
Username: loc
|
||||
Password:
|
||||
Login Succeeded
|
||||
|
||||
loc@Devbox:~/mycerts$ docker push <Harbor FQDN or static IP>/test/busybox
|
||||
The push refers to a repository [<Harbor FQDN or static IP>/test/busybox]
|
||||
e88b3f82283b: Pushed
|
||||
latest: digest: sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912 size: 527
|
||||
```
|
||||
|
||||
## Pull the container image down to the VCH
|
||||
|
||||
Now, in another terminal, we can pull the image from Harbor to our VCH.
|
||||
|
||||
```
|
||||
loc@Devbox:~$ export DOCKER_HOST=tcp://<Deployed VCH IP>:2375
|
||||
loc@Devbox:~$ export DOCKER_API_VERSION=1.23
|
||||
loc@Devbox:~$ docker images
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
|
||||
loc@Devbox:~$ docker pull <Harbor FQDN or static IP>/test/busybox
|
||||
Using default tag: latest
|
||||
Pulling from test/busybox
|
||||
Error: image test/busybox not found
|
||||
|
||||
loc@Devbox:~$ docker login <Harbor FQDN or static IP>
|
||||
Username: loc
|
||||
Password:
|
||||
Login Succeeded
|
||||
|
||||
loc@Devbox:~$ docker pull <Harbor FQDN or static IP>/test/busybox
|
||||
Using default tag: latest
|
||||
Pulling from test/busybox
|
||||
56bec22e3559: Pull complete
|
||||
a3ed95caeb02: Pull complete
|
||||
Digest: sha256:97af7f861fb557c1eaafb721946af5c7aefaedd51f78d38fa1828d7ccaae4141
|
||||
Status: Downloaded newer image for test/busybox:latest
|
||||
|
||||
loc@Devbox:~$ docker images
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
<Harbor FQDN or static IP>/test/busybox latest e292aa76ad3b 5 weeks ago 1.093 MB
|
||||
loc@Devbox:~$
|
||||
```
|
||||
|
||||
Note above, on our first attempt to pull the image down, it failed, with a 'not found' error message. Once we log into the Harbor server, our attempt to pull down the image succeeds.
|
||||
BIN
vendor/github.com/vmware/vic/doc/application_workflow/images/elgg.png
generated
vendored
Normal file
BIN
vendor/github.com/vmware/vic/doc/application_workflow/images/elgg.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user