2020-02-14 09:39:16 +00:00
/ *
Copyright 2020 The Kubernetes Authors 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 oci
import (
2020-02-20 05:18:07 +00:00
"bufio"
"bytes"
2020-02-20 07:02:36 +00:00
"fmt"
2020-02-14 09:39:16 +00:00
"os/exec"
2020-02-20 07:02:36 +00:00
"strings"
2020-02-14 09:39:16 +00:00
2020-02-16 19:54:56 +00:00
"github.com/golang/glog"
2020-02-14 09:39:16 +00:00
"github.com/pkg/errors"
2020-02-21 20:01:03 +00:00
"k8s.io/minikube/pkg/minikube/preload"
2020-02-14 09:39:16 +00:00
)
2020-02-17 18:55:53 +00:00
// DeleteAllVolumesByLabel deletes all volumes that have a specific label
// if there is no volume to delete it will return nil
2020-02-19 22:31:08 +00:00
func DeleteAllVolumesByLabel ( ociBin string , label string ) [ ] error {
var deleteErrs [ ] error
2020-02-20 05:18:07 +00:00
glog . Infof ( "trying to delete all %s volumes with label %s" , ociBin , label )
2020-02-14 09:39:16 +00:00
if ociBin == Docker {
if err := PointToHostDockerDaemon ( ) ; err != nil {
2020-02-19 22:31:08 +00:00
return [ ] error { errors . Wrap ( err , "point host docker-daemon" ) }
2020-02-14 09:39:16 +00:00
}
}
2020-02-19 22:31:08 +00:00
vs , err := allVolumesByLabel ( ociBin , label )
if err != nil {
2020-02-20 05:18:07 +00:00
return [ ] error { fmt . Errorf ( "listing volumes by label %q: %v" , label , err ) }
2020-02-19 22:31:08 +00:00
}
2020-02-20 05:18:07 +00:00
2020-02-19 23:18:18 +00:00
for _ , v := range vs {
2020-02-19 23:22:22 +00:00
cmd := exec . Command ( ociBin , "volume" , "rm" , "--force" , v )
if out , err := cmd . CombinedOutput ( ) ; err != nil {
2020-02-20 05:18:07 +00:00
deleteErrs = append ( deleteErrs , fmt . Errorf ( "deleting volume %s: output: %s" , v , string ( out ) ) )
}
}
return deleteErrs
}
// PruneAllVolumesByLabel deletes all volumes that have a specific label
// if there is no volume to delete it will return nil
2020-02-14 09:39:16 +00:00
// example: docker volume prune -f --filter label=name.minikube.sigs.k8s.io=minikube
2020-02-20 05:18:07 +00:00
func PruneAllVolumesByLabel ( ociBin string , label string ) [ ] error {
var deleteErrs [ ] error
2020-02-16 19:54:56 +00:00
glog . Infof ( "trying to prune all %s volumes with label %s" , ociBin , label )
2020-02-14 09:39:16 +00:00
if ociBin == Docker {
if err := PointToHostDockerDaemon ( ) ; err != nil {
2020-02-20 05:18:07 +00:00
return [ ] error { errors . Wrap ( err , "point host docker-daemon" ) }
2020-02-14 09:39:16 +00:00
}
}
2020-02-19 22:31:08 +00:00
// try to prune afterwards just in case delete didn't go through
cmd := exec . Command ( ociBin , "volume" , "prune" , "-f" , "--filter" , "label=" + label )
2020-02-14 09:39:16 +00:00
if out , err := cmd . CombinedOutput ( ) ; err != nil {
2020-02-20 05:18:07 +00:00
deleteErrs = append ( deleteErrs , errors . Wrapf ( err , "prune volume by label %s: %s" , label , string ( out ) ) )
2020-02-14 09:39:16 +00:00
}
2020-02-19 22:31:08 +00:00
return deleteErrs
}
// allVolumesByLabel returns name of all docker volumes by a specific label
// will not return error if there is no volume found.
func allVolumesByLabel ( ociBin string , label string ) ( [ ] string , error ) {
cmd := exec . Command ( ociBin , "volume" , "ls" , "--filter" , "label=" + label , "--format" , "{{.Name}}" )
stdout , err := cmd . Output ( )
2020-02-20 05:18:07 +00:00
s := bufio . NewScanner ( bytes . NewReader ( stdout ) )
2020-02-19 23:05:25 +00:00
var vols [ ] string
2020-02-20 05:18:07 +00:00
for s . Scan ( ) {
v := strings . TrimSpace ( s . Text ( ) )
if v != "" {
vols = append ( vols , v )
2020-02-19 23:22:22 +00:00
}
2020-02-19 22:31:08 +00:00
}
2020-02-19 23:05:25 +00:00
return vols , err
2020-02-14 09:39:16 +00:00
}
2020-02-20 17:42:04 +00:00
// CreatePreloadedImagesVolume creates a volume with preloaded images
2020-02-21 20:01:03 +00:00
// k8sVersion is used to name the volume and baseImage is the image that is run
// to extract the preloaded images to the volume
2020-02-21 20:53:24 +00:00
func CreatePreloadedImagesVolume ( k8sVersion , cRuntime , baseImage , profile string ) ( string , error ) {
if cRuntime != "docker" {
return "" , nil
}
2020-02-20 07:02:36 +00:00
if err := PointToHostDockerDaemon ( ) ; err != nil {
return "" , errors . Wrap ( err , "point host docker-daemon" )
}
2020-02-21 20:41:33 +00:00
volumeName := preloadedVolumeName ( k8sVersion , profile )
2020-02-20 07:02:36 +00:00
if dockerVolumeExists ( volumeName ) {
return volumeName , nil
}
if err := createDockerVolume ( volumeName ) ; err != nil {
return "" , errors . Wrap ( err , "creating docker volume" )
}
2020-02-21 20:01:03 +00:00
tarballPath := preload . TarballFilepath ( k8sVersion )
2020-02-20 07:02:36 +00:00
2020-02-20 22:43:24 +00:00
if err := extractTarballToVolume ( tarballPath , volumeName , baseImage ) ; err != nil {
2020-02-21 20:13:34 +00:00
// If the extraction didn't work, delete the corrupt docker volume
if err := deleteDockerVolume ( volumeName ) ; err != nil {
glog . Warningf ( "Corrupt docker volume %s was not deleted successfully. You may need to delete it manually via `docker volume rm %s` for minikube to continue to work." , volumeName , volumeName )
}
2020-02-20 07:02:36 +00:00
return "" , errors . Wrap ( err , "extracting tarball to volume" )
}
return volumeName , nil
}
2020-02-21 20:01:03 +00:00
// dockerVolumeExists returns true if a docker volume with the passed in name exists
2020-02-20 07:02:36 +00:00
func dockerVolumeExists ( name string ) bool {
if err := PointToHostDockerDaemon ( ) ; err != nil {
return false
}
cmd := exec . Command ( Docker , "volume" , "ls" , "-q" )
out , err := cmd . CombinedOutput ( )
if err != nil {
return false
}
names := strings . Split ( string ( out ) , "\n" )
for _ , n := range names {
2020-02-21 20:01:03 +00:00
if strings . TrimSpace ( n ) == name {
2020-02-20 07:02:36 +00:00
return true
}
}
return false
}
2020-02-21 20:01:03 +00:00
// extractTarballToVolume runs a docker image imageName which extracts the tarball at tarballPath
// to the volume named volumeName
2020-02-20 22:43:24 +00:00
func extractTarballToVolume ( tarballPath , volumeName , imageName string ) error {
2020-02-20 07:02:36 +00:00
if err := PointToHostDockerDaemon ( ) ; err != nil {
return errors . Wrap ( err , "point host docker-daemon" )
}
2020-02-20 22:59:43 +00:00
cmd := exec . Command ( Docker , "run" , "--rm" , "--entrypoint" , "/usr/bin/tar" , "-v" , fmt . Sprintf ( "%s:/preloaded.tar:ro" , tarballPath ) , "-v" , fmt . Sprintf ( "%s:/extractDir" , volumeName ) , imageName , "-I" , "lz4" , "-xvf" , "/preloaded.tar" , "-C" , "/extractDir" )
2020-02-20 07:02:36 +00:00
if out , err := cmd . CombinedOutput ( ) ; err != nil {
return errors . Wrapf ( err , "output %s" , string ( out ) )
}
return nil
}
2020-02-14 09:39:16 +00:00
// createDockerVolume creates a docker volume to be attached to the container with correct labels and prefixes based on profile name
// Caution ! if volume already exists does NOT return an error and will not apply the minikube labels on it.
// TODO: this should be fixed as a part of https://github.com/kubernetes/minikube/issues/6530
func createDockerVolume ( name string ) error {
if err := PointToHostDockerDaemon ( ) ; err != nil {
return errors . Wrap ( err , "point host docker-daemon" )
}
2020-02-19 21:26:24 +00:00
cmd := exec . Command ( Docker , "volume" , "create" , name , "--label" , fmt . Sprintf ( "%s=%s" , ProfileLabelKey , name ) , "--label" , fmt . Sprintf ( "%s=%s" , CreatedByLabelKey , "true" ) )
2020-02-14 09:39:16 +00:00
if out , err := cmd . CombinedOutput ( ) ; err != nil {
return errors . Wrapf ( err , "output %s" , string ( out ) )
}
return nil
}
2020-02-21 20:13:34 +00:00
// deleteDockerVolume deletes a docker volume with the given name
func deleteDockerVolume ( name string ) error {
if err := PointToHostDockerDaemon ( ) ; err != nil {
return errors . Wrap ( err , "point host docker-daemon" )
}
cmd := exec . Command ( Docker , "volume" , "rm" , name )
if out , err := cmd . CombinedOutput ( ) ; err != nil {
return errors . Wrapf ( err , "output %s" , string ( out ) )
}
return nil
}
2020-02-21 20:41:33 +00:00
func preloadedVolumeName ( k8sVersion , profile string ) string {
return fmt . Sprintf ( "k8s-%s-%s" , k8sVersion , profile )
}
// PreloadedVolumeAttached returns true if the preloaded volume is attached
// to the running profile
func PreloadedVolumeAttached ( k8sVersion , profile string ) bool {
glog . Infof ( "Checking if preloaded volume is attached to %s" , profile )
if err := PointToHostDockerDaemon ( ) ; err != nil {
glog . Infof ( "error pointing host to docker daemon: %v" , err )
return false
}
volumeName := preloadedVolumeName ( k8sVersion , profile )
cmd := exec . Command ( Docker , "inspect" , "-f" , "{{range .Mounts}} {{.Name}} {{end}}" , profile )
out , err := cmd . CombinedOutput ( )
if err != nil {
glog . Infof ( "error inspecting mounted volumes: %v" , err )
return false
}
vols := strings . Split ( string ( out ) , " " )
for _ , v := range vols {
if strings . TrimSpace ( v ) == volumeName {
return true
}
}
return false
}