Add storage helpers
- add storage and artifact types - extract fs operations to storage functions - construct artifact paths from Kubernetes objects metadata
This commit is contained in:
		
							parent
							
								
									8cd8d8f6f2
								
							
						
					
					
						commit
						d1fb8e1ade
					
				|  | @ -0,0 +1,118 @@ | ||||||
|  | package controllers | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Storage manages artifacts
 | ||||||
|  | type Storage struct { | ||||||
|  | 	// BasePath is the local directory path where the source artifacts are stored.
 | ||||||
|  | 	BasePath string `json:"basePath"` | ||||||
|  | 
 | ||||||
|  | 	// Hostname is the file server host name used to compose the artifacts URIs.
 | ||||||
|  | 	Hostname string `json:"hostname"` | ||||||
|  | 
 | ||||||
|  | 	// Timeout for artifacts operations
 | ||||||
|  | 	Timeout time.Duration `json:"timeout"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Artifact represents the output of a source synchronisation
 | ||||||
|  | type Artifact struct { | ||||||
|  | 	// Path is the local file path of this artifact
 | ||||||
|  | 	Path string `json:"path"` | ||||||
|  | 
 | ||||||
|  | 	// URL is the HTTP address of this artifact
 | ||||||
|  | 	URL string `json:"url"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewStorage creates the storage helper for a given path and hostname
 | ||||||
|  | func NewStorage(basePath string, hostname string, timeout time.Duration) (*Storage, error) { | ||||||
|  | 	if f, err := os.Stat(basePath); os.IsNotExist(err) || !f.IsDir() { | ||||||
|  | 		return nil, fmt.Errorf("invalid dir path %s", basePath) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &Storage{ | ||||||
|  | 		BasePath: basePath, | ||||||
|  | 		Hostname: hostname, | ||||||
|  | 		Timeout:  timeout, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ArtifactFor returns an artifact for the given Kubernetes object
 | ||||||
|  | func (s *Storage) ArtifactFor(kind string, metadata metav1.Object, fileName string) Artifact { | ||||||
|  | 	path := fmt.Sprintf("%s/%s-%s/%s", kind, metadata.GetName(), metadata.GetNamespace(), fileName) | ||||||
|  | 	localPath := filepath.Join(s.BasePath, path) | ||||||
|  | 	url := fmt.Sprintf("http://%s/%s", s.Hostname, path) | ||||||
|  | 
 | ||||||
|  | 	return Artifact{ | ||||||
|  | 		Path: localPath, | ||||||
|  | 		URL:  url, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MkdirAll calls os.MkdirAll for the given artifact base dir
 | ||||||
|  | func (s *Storage) MkdirAll(artifact Artifact) error { | ||||||
|  | 	dir := filepath.Dir(artifact.Path) | ||||||
|  | 	return os.MkdirAll(dir, 0777) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RemoveAll calls os.RemoveAll for the given artifact base dir
 | ||||||
|  | func (s *Storage) RemoveAll(artifact Artifact) error { | ||||||
|  | 	dir := filepath.Dir(artifact.Path) | ||||||
|  | 	return os.RemoveAll(dir) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RemoveAllButCurrent removes all files for the given artifact base dir excluding the current one
 | ||||||
|  | func (s *Storage) RemoveAllButCurrent(artifact Artifact) error { | ||||||
|  | 	dir := filepath.Dir(artifact.Path) | ||||||
|  | 	errors := []string{} | ||||||
|  | 	_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { | ||||||
|  | 		if path != artifact.Path { | ||||||
|  | 			if err := os.Remove(path); err != nil { | ||||||
|  | 				errors = append(errors, info.Name()) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	if len(errors) > 0 { | ||||||
|  | 		return fmt.Errorf("faild to remove files: %s", strings.Join(errors, " ")) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ArtifactExist returns a boolean indicating whether the artifact file exists in storage
 | ||||||
|  | func (s *Storage) ArtifactExist(artifact Artifact) bool { | ||||||
|  | 	if _, err := os.Stat(artifact.Path); os.IsNotExist(err) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Archive creates a tar.gz to the artifact path from the given dir excluding the provided file extensions
 | ||||||
|  | func (s *Storage) Archive(artifact Artifact, dir string, excludes string) error { | ||||||
|  | 	if excludes == "" { | ||||||
|  | 		excludes = "jpg,jpeg,gif,png,wmv,flv,tar.gz,zip" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ctx, cancel := context.WithTimeout(context.Background(), s.Timeout) | ||||||
|  | 	defer cancel() | ||||||
|  | 
 | ||||||
|  | 	tarExcludes := fmt.Sprintf("--exclude=\\*.{%s} --exclude .git", excludes) | ||||||
|  | 	cmd := fmt.Sprintf("cd %s && tar -c %s -f - . | gzip > %s", dir, tarExcludes, artifact.Path) | ||||||
|  | 	command := exec.CommandContext(ctx, "/bin/sh", "-c", cmd) | ||||||
|  | 
 | ||||||
|  | 	err := command.Run() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("command '%s' failed: %w", cmd, err) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue