server.garden privileged automation agent (mirror of https://git.sequentialread.com/forest/rootsystem)
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
729 lines
22 KiB
729 lines
22 KiB
package automation |
|
|
|
import ( |
|
"fmt" |
|
"io/ioutil" |
|
"os" |
|
"path/filepath" |
|
"regexp" |
|
"runtime" |
|
"sort" |
|
"strings" |
|
|
|
errors "git.sequentialread.com/forest/pkg-errors" |
|
"git.sequentialread.com/forest/rootsystem/configuration" |
|
) |
|
|
|
type TerraformConfiguration struct { |
|
BuildNumber int |
|
TargetedModules []string |
|
TerraformProject string |
|
RemoteState string |
|
RemoteStateVariables []string |
|
HostKeysObjectStorageCredentials []configuration.Credential |
|
} |
|
|
|
type tfProvider struct { |
|
Name string |
|
Version string |
|
CredentialType string |
|
UsernamePropertyName string |
|
PasswordPropertyName string |
|
} |
|
|
|
type tfModule struct { |
|
Name string |
|
Providers []string |
|
Arguments []*tfVariable |
|
Attributes []*tfVariable |
|
} |
|
|
|
type tfVariable struct { |
|
Name string |
|
IsList bool |
|
ProvidedBy []*tfProvidedBy |
|
} |
|
|
|
type tfProvidedBy struct { |
|
Module string |
|
Attribute string |
|
Variable string |
|
RemoteStateOutput string |
|
} |
|
|
|
const ssh_public_keys = "ssh_public_keys" |
|
const ssh_private_key_filepath = "ssh_private_key_filepath" |
|
const node_id = "node_id" |
|
const node_arch = "node_arch" |
|
const post_to_object_storage_shell_script = "post_to_object_storage_shell_script" |
|
const ssh_private_key_filepath_value = "ssh/servergarden_builtin_ed22519" |
|
const remote_state_placeholder_text = "<previous build>" |
|
const ssh_public_keys_placeholder_text = "<secure shell public keys>" |
|
const post_to_object_storage_shell_script_placeholder_text = "<long shell script>" |
|
|
|
func setHardcodedVariables(config *configuration.Configuration, variables map[string]string) { |
|
variables[ssh_public_keys] = ssh_public_keys_placeholder_text |
|
variables[node_id] = config.Host.Name |
|
variables[node_arch] = runtime.GOARCH |
|
variables[ssh_private_key_filepath] = ssh_private_key_filepath_value |
|
variables[post_to_object_storage_shell_script] = post_to_object_storage_shell_script_placeholder_text |
|
} |
|
|
|
func WriteTerraformCodeForTargetedModules( |
|
config *configuration.Configuration, |
|
workingDirectory string, |
|
terraformConfig TerraformConfiguration, |
|
) ([]string, error) { |
|
|
|
providers := map[string]tfProvider{ |
|
"digitalocean": tfProvider{ |
|
Name: "digitalocean", |
|
Version: "1.15", |
|
CredentialType: configuration.DIGITALOCEAN, |
|
PasswordPropertyName: "token", |
|
}, |
|
"gandi": tfProvider{ |
|
Name: "gandi", |
|
CredentialType: configuration.GANDI, |
|
PasswordPropertyName: "key", |
|
}, |
|
} |
|
|
|
modules, err := parseModulesFolder(providers, workingDirectory) |
|
if err != nil { |
|
return []string{}, errors.Wrap(err, "can't WriteTerraformCodeForTargetedModules because can't parseModulesFolder()") |
|
} |
|
|
|
usedProviders := make(map[string]tfProvider) |
|
for _, provider := range providers { |
|
for _, credential := range config.Credentials { |
|
if provider.CredentialType == credential.Type { |
|
usedProviders[provider.Name] = provider |
|
} |
|
} |
|
} |
|
|
|
usedModules := make(map[string]tfModule, 0) |
|
for _, module := range modules { |
|
missingOne := false |
|
for _, providerName := range module.Providers { |
|
if _, has := usedProviders[providerName]; !has { |
|
missingOne = true |
|
} |
|
} |
|
if !missingOne { |
|
usedModules[module.Name] = module |
|
} |
|
} |
|
|
|
// these variables don't have to have the real values, but all used variables do have to be |
|
// included in this map so we can wire up the modules correctly. |
|
allVariables := map[string]string{} |
|
for k, v := range config.Terraform.Variables { |
|
allVariables[k] = v |
|
} |
|
setHardcodedVariables(config, allVariables) |
|
|
|
err = fillOutProvidedByOnAttributes(usedModules, allVariables, terraformConfig.RemoteStateVariables) |
|
if err != nil { |
|
return []string{}, errors.Wrap(err, "can't WriteTerraformCodeForTargetedModules because can't fillOutProvidedByOnAttributes") |
|
} |
|
|
|
modulesToCreate := map[string]bool{} |
|
for _, target := range terraformConfig.TargetedModules { |
|
module, has := usedModules[target] |
|
if !has { |
|
return []string{}, errors.Wrapf( |
|
err, |
|
`can't WriteTerraformCodeForTargetedModules because the TargetedModule \"%s\" |
|
was not found in the list of modules that we have authenticated providers for. |
|
Missing credentials or incorrect module name.`, |
|
target, |
|
) |
|
} |
|
|
|
dependencies, err := getDependencies(module, usedModules, nil) |
|
if !has { |
|
return []string{}, errors.Wrapf(err, `can't WriteTerraformCodeForTargetedModules because cant getDependencies for module "%s"`, module.Name) |
|
} |
|
|
|
modulesToCreate[module.Name] = true |
|
for _, moduleName := range dependencies { |
|
modulesToCreate[moduleName] = true |
|
} |
|
} |
|
|
|
providerStanzas := make([]string, 0) |
|
for _, provider := range usedProviders { |
|
for _, credential := range config.Credentials { |
|
if provider.CredentialType == credential.Type { |
|
versionProperty := "" |
|
usernameProperty := "" |
|
passwordProperty := "" |
|
if provider.UsernamePropertyName != "" { |
|
usernameProperty = fmt.Sprintf( |
|
` |
|
%s = "%s" |
|
`, |
|
provider.UsernamePropertyName, |
|
credential.Username, |
|
) |
|
} |
|
if provider.PasswordPropertyName != "" { |
|
passwordProperty = fmt.Sprintf( |
|
` |
|
%s = "%s" |
|
`, |
|
provider.PasswordPropertyName, |
|
credential.Password, |
|
) |
|
} |
|
if provider.Version != "" { |
|
versionProperty = fmt.Sprintf( |
|
` |
|
version = "%s" |
|
`, |
|
provider.Version, |
|
) |
|
} |
|
providerStanzas = append(providerStanzas, fmt.Sprintf( |
|
` |
|
provider "%s" {%s%s%s} |
|
`, |
|
provider.Name, |
|
versionProperty, |
|
usernameProperty, |
|
passwordProperty, |
|
)) |
|
} |
|
} |
|
} |
|
|
|
sshPublicKeyObjectStrings := make([]string, 0) |
|
|
|
sshPublicKeys, err := getSSHPublicKeys(workingDirectory) |
|
if err != nil { |
|
return []string{}, errors.Wrap(err, "can't WriteTerraformCodeForTargetedModules because can't getSSHPublicKeys") |
|
} |
|
|
|
for _, sshPublicKey := range sshPublicKeys { |
|
sshPublicKeyObjectStrings = append(sshPublicKeyObjectStrings, fmt.Sprintf( |
|
`{ |
|
name = "%s" |
|
filepath = "%s" |
|
}`, |
|
sshPublicKey[1], |
|
sshPublicKey[0], |
|
)) |
|
} |
|
|
|
// we can't use the allVariables here like we used above, because we have to handle variable values |
|
// that are a list of objects, not just a string. |
|
// so we special case it below this loop. |
|
variableStanzas := make([]string, 0) |
|
for key, value := range config.Terraform.Variables { |
|
if strings.Contains(value, "\n") { |
|
variableStanzas = append(variableStanzas, fmt.Sprintf(` |
|
variable "%s" { |
|
default = <<EOT |
|
%s |
|
EOT |
|
} |
|
`, key, value, |
|
)) |
|
} else { |
|
variableStanzas = append(variableStanzas, fmt.Sprintf(` |
|
variable "%s" { default = "%s" } |
|
`, key, value, |
|
)) |
|
} |
|
} |
|
|
|
postToObjectStorageShellScript := "" |
|
if terraformConfig.HostKeysObjectStorageCredentials != nil { |
|
postToObjectStorageShellScript, err = getPostToObjectStorageShellScript( |
|
config, |
|
terraformConfig.HostKeysObjectStorageCredentials, |
|
) |
|
if err != nil { |
|
return []string{}, errors.Wrap(err, "can't WriteTerraformCodeForTargetedModules because") |
|
} |
|
} |
|
|
|
variableStanzas = append(variableStanzas, |
|
fmt.Sprintf(` |
|
variable "%s" { |
|
type = list(object({ |
|
name = string |
|
filepath = string |
|
})) |
|
default = [%s] |
|
} |
|
`, ssh_public_keys, strings.Join(sshPublicKeyObjectStrings, ",\n"), |
|
), |
|
fmt.Sprintf(` |
|
variable "%s" { default = "%s" } |
|
`, node_id, config.Host.Name, |
|
), |
|
|
|
fmt.Sprintf(` |
|
variable "%s" { default = "%s" } |
|
`, node_arch, runtime.GOARCH, |
|
), |
|
fmt.Sprintf(` |
|
variable "%s" { default = "%s" } |
|
`, ssh_private_key_filepath, filepath.Join(workingDirectory, ssh_private_key_filepath_value), |
|
), |
|
fmt.Sprintf(` |
|
variable "%s" { |
|
default = <<EOT |
|
%s |
|
EOT |
|
} |
|
`, post_to_object_storage_shell_script, postToObjectStorageShellScript, |
|
), |
|
) |
|
|
|
moduleStanzas := make([]string, 0) |
|
for moduleName := range modulesToCreate { |
|
module := usedModules[moduleName] |
|
argumentLines := []string{} |
|
|
|
providedByToString := func(provision *tfProvidedBy) string { |
|
if provision.Variable != "" { |
|
return fmt.Sprintf("var.%s", provision.Variable) |
|
} else if provision.RemoteStateOutput != "" { |
|
return fmt.Sprintf( |
|
"data.terraform_remote_state.%s.outputs.%s", |
|
terraformConfig.RemoteState, provision.RemoteStateOutput, |
|
) |
|
} else { |
|
return fmt.Sprintf("module.%s.%s", provision.Module, provision.Attribute) |
|
} |
|
} |
|
|
|
for _, argument := range module.Arguments { |
|
if !argument.IsList { |
|
var usedProvision *tfProvidedBy = nil |
|
providedByVariable := false |
|
providedByModules := []string{} |
|
for _, provision := range argument.ProvidedBy { |
|
if provision.Variable != "" { |
|
providedByVariable = true |
|
usedProvision = provision |
|
} |
|
if provision.RemoteStateOutput != "" { |
|
usedProvision = provision |
|
} |
|
if provision.Module != "" { |
|
if usedProvision == nil { |
|
usedProvision = provision |
|
} |
|
providedByModules = append(providedByModules, provision.Module) |
|
} |
|
} |
|
if providedByVariable && len(providedByModules) > 0 { |
|
return []string{}, |
|
fmt.Errorf( |
|
"TODO: what do do in this case? a non-list argument %s is provided by both a variable and a module (%s)", |
|
argument.Name, |
|
providedByModules[0], |
|
) |
|
} |
|
if len(providedByModules) > 1 { |
|
return []string{}, |
|
fmt.Errorf( |
|
"TODO: what do do in this case? a non-list argument %s is provided by more than one module: [%s]", |
|
argument.Name, |
|
strings.Join(providedByModules, ", "), |
|
) |
|
} |
|
if usedProvision == nil { |
|
return []string{}, fmt.Errorf("argument %s is not provided by any variables or modules", argument.Name) |
|
} |
|
argumentLines = append(argumentLines, fmt.Sprintf( |
|
` |
|
%s = %s |
|
`, |
|
argument.Name, |
|
providedByToString(usedProvision), |
|
)) |
|
} else { |
|
fixedProvidedBy := removeRedundantModuleProvision(argument.ProvidedBy) |
|
provisionStrings := []string{} |
|
for _, provision := range fixedProvidedBy { |
|
provisionStrings = append(provisionStrings, providedByToString(provision)) |
|
} |
|
sort.Strings(provisionStrings) |
|
argumentLines = append(argumentLines, fmt.Sprintf( |
|
` |
|
%s = [ |
|
%s |
|
] |
|
`, |
|
argument.Name, |
|
strings.Join(provisionStrings, ",\n "), |
|
)) |
|
} |
|
} |
|
|
|
moduleStanzas = append(moduleStanzas, fmt.Sprintf( |
|
` |
|
module "%s" { |
|
source = "./modules/%s" |
|
%s |
|
} |
|
`, |
|
module.Name, |
|
module.Name, |
|
strings.Join(argumentLines, "\n"), |
|
)) |
|
} |
|
|
|
outputStanzas := []string{} |
|
outputVariables := []string{} |
|
for moduleName := range modulesToCreate { |
|
module := usedModules[moduleName] |
|
for _, attribute := range module.Attributes { |
|
outputVariables = append(outputVariables, attribute.Name) |
|
outputStanzas = append(outputStanzas, fmt.Sprintf(` |
|
output "%s" { |
|
value = module.%s.%s |
|
} |
|
`, attribute.Name, module.Name, attribute.Name)) |
|
} |
|
} |
|
|
|
terraformFolder := filepath.Join(workingDirectory, terraformConfig.TerraformProject) |
|
if _, err := os.Stat(terraformFolder); os.IsNotExist(err) { |
|
err = os.Mkdir(terraformFolder, 0700) |
|
if err != nil { |
|
return []string{}, errors.Wrapf(err, "can't initializeTerraformProject because can't os.Mkdir(\"%s\")", terraformFolder) |
|
} |
|
} |
|
terraformCodeFilepath := filepath.Join(terraformFolder, "main.tf") |
|
terraformCodeFile, err := os.OpenFile(terraformCodeFilepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700) |
|
if err != nil { |
|
return []string{}, errors.Wrapf(err, "can't initializeTerraformProject because can't os.OpenFile(\"%s\")", terraformCodeFilepath) |
|
} |
|
|
|
terraformModulesFolder := filepath.Join(terraformFolder, "modules") |
|
|
|
os.Remove(terraformModulesFolder) |
|
err = os.Symlink( |
|
filepath.Join(workingDirectory, configuration.TERRAFORM_MODULES), |
|
terraformModulesFolder, |
|
) |
|
if err != nil { |
|
return []string{}, errors.Wrapf(err, "could not create symbolic link to terraform modules folder") |
|
} |
|
|
|
remoteState := "" |
|
if terraformConfig.RemoteState != "" { |
|
remoteState = fmt.Sprintf(` |
|
data "terraform_remote_state" "%s" { |
|
backend = "http" |
|
config = { |
|
address = "http://localhost:6471/%s" |
|
} |
|
} |
|
`, |
|
terraformConfig.RemoteState, |
|
terraformConfig.RemoteState, |
|
) |
|
} |
|
|
|
fmt.Fprintf( |
|
terraformCodeFile, |
|
` |
|
terraform { |
|
backend "http" { |
|
address = "http://localhost:%d/%s" |
|
} |
|
} |
|
%s |
|
%s |
|
%s |
|
%s |
|
%s |
|
`, |
|
configuration.TERRAFORM_STATE_SERVER_PORT_NUMBER, |
|
terraformConfig.TerraformProject, |
|
remoteState, |
|
strings.Join(providerStanzas, "\n"), |
|
strings.Join(variableStanzas, "\n"), |
|
strings.Join(moduleStanzas, "\n"), |
|
strings.Join(outputStanzas, "\n"), |
|
) |
|
|
|
return outputVariables, nil |
|
} |
|
|
|
func removeRedundantModuleProvision(provisions []*tfProvidedBy) []*tfProvidedBy { |
|
provisionMap := map[string]*tfProvidedBy{} |
|
for _, provision := range provisions { |
|
name := provision.Attribute |
|
if name == "" { |
|
name = provision.Variable |
|
} |
|
if name == "" { |
|
name = provision.RemoteStateOutput |
|
} |
|
_, has := provisionMap[name] |
|
if !has || provision.Variable != "" || provision.RemoteStateOutput != "" { |
|
provisionMap[name] = provision |
|
} |
|
} |
|
toReturn := []*tfProvidedBy{} |
|
for _, provision := range provisionMap { |
|
toReturn = append(toReturn, provision) |
|
} |
|
return toReturn |
|
} |
|
|
|
func provides(attributeName string, argument *tfVariable) bool { |
|
argumentNamePrefix := regexp.MustCompile("[-_]list$").ReplaceAllString(argument.Name, "") |
|
listMatch := argument.IsList && strings.HasPrefix(attributeName, argumentNamePrefix) |
|
return attributeName == argument.Name || listMatch |
|
} |
|
|
|
func fillOutProvidedByOnAttributes( |
|
modules map[string]tfModule, |
|
variables map[string]string, |
|
remoteStateOutputs []string, |
|
) error { |
|
for _, module := range modules { |
|
for _, argument := range module.Arguments { |
|
argument.ProvidedBy = make([]*tfProvidedBy, 0) |
|
|
|
for variableName := range variables { |
|
if provides(variableName, argument) { |
|
argument.ProvidedBy = append(argument.ProvidedBy, &tfProvidedBy{Variable: variableName}) |
|
} |
|
} |
|
|
|
for _, outputName := range remoteStateOutputs { |
|
if provides(outputName, argument) { |
|
argument.ProvidedBy = append(argument.ProvidedBy, &tfProvidedBy{RemoteStateOutput: outputName}) |
|
} |
|
} |
|
|
|
for _, otherModule := range modules { |
|
if otherModule.Name != module.Name { |
|
for _, otherModuleAttribute := range otherModule.Attributes { |
|
if provides(otherModuleAttribute.Name, argument) { |
|
argument.ProvidedBy = append(argument.ProvidedBy, &tfProvidedBy{ |
|
Module: otherModule.Name, |
|
Attribute: otherModuleAttribute.Name, |
|
}) |
|
} |
|
} |
|
} |
|
} |
|
|
|
if len(argument.ProvidedBy) == 0 { |
|
return fmt.Errorf( |
|
`can't find any variables or module attributes to provide argument %s on module %s. |
|
Are you missing a variable, or credentials for a provider that is required for one of your modules or one of its dependencies?`, |
|
argument.Name, |
|
module.Name, |
|
) |
|
} |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func getDependencies( |
|
module tfModule, |
|
modules map[string]tfModule, |
|
visited map[string]bool, |
|
) ([]string, error) { |
|
|
|
if visited == nil { |
|
visited = map[string]bool{} |
|
visited[module.Name] = true |
|
} |
|
dependencies := make([]string, 0) |
|
|
|
for _, argument := range module.Arguments { |
|
|
|
fixedProvidedBy := removeRedundantModuleProvision(argument.ProvidedBy) |
|
|
|
for _, provision := range fixedProvidedBy { |
|
requiresModule := provision.RemoteStateOutput == "" && provision.Variable == "" |
|
if requiresModule && provision.Module != "" && !visited[provision.Module] { |
|
visited[provision.Module] = true |
|
dependencies = append(dependencies, provision.Module) |
|
otherModule, has := modules[provision.Module] |
|
if !has { |
|
return nil, fmt.Errorf( |
|
"can't get transitiveDependencies for module \"%s\": other module \"%s\" does not exist", |
|
module.Name, |
|
provision.Module, |
|
) |
|
} |
|
transitiveDependencies, err := getDependencies(otherModule, modules, visited) |
|
if err != nil { |
|
return nil, errors.Wrapf(err, "can't get transitiveDependencies for module %s", provision.Module) |
|
} |
|
dependencies = append(dependencies, transitiveDependencies...) |
|
} |
|
} |
|
} |
|
|
|
return dependencies, nil |
|
} |
|
|
|
func parseModulesFolder(providers map[string]tfProvider, workingDirectory string) ([]tfModule, error) { |
|
|
|
modules := make([]tfModule, 0) |
|
|
|
// TODO make sure modules folder and all the code inside is owned by root and only writable by root |
|
modulesFolder := filepath.Join(workingDirectory, configuration.TERRAFORM_MODULES) |
|
modulesFileInfos, err := ioutil.ReadDir(modulesFolder) |
|
if err != nil { |
|
return nil, errors.Wrapf(err, "can't parseModulesFolder because can't ioutil.ReadDir(\"%s\")", modulesFolder) |
|
} |
|
|
|
for _, fileInfo := range modulesFileInfos { |
|
if fileInfo.IsDir() { |
|
moduleName := fileInfo.Name() |
|
|
|
module := tfModule{ |
|
Name: moduleName, |
|
Providers: make([]string, 0), |
|
Arguments: make([]*tfVariable, 0), |
|
Attributes: make([]*tfVariable, 0), |
|
} |
|
|
|
for _, provider := range providers { |
|
if strings.Contains(moduleName, provider.Name) { |
|
module.Providers = append(module.Providers, provider.Name) |
|
} |
|
} |
|
|
|
moduleFolder := filepath.Join(modulesFolder, moduleName) |
|
moduleCodeFileInfos, err := ioutil.ReadDir(moduleFolder) |
|
if err != nil { |
|
return nil, errors.Wrapf(err, "can't parseModulesFolder because can't ioutil.ReadDir(\"%s\")", moduleFolder) |
|
} |
|
|
|
for _, fileInfo := range moduleCodeFileInfos { |
|
filepath := filepath.Join(moduleFolder, fileInfo.Name()) |
|
if strings.HasSuffix(filepath, ".tf") { |
|
bytes, err := ioutil.ReadFile(filepath) |
|
if err != nil { |
|
return nil, errors.Wrapf(err, "can't parseModulesFolder because can't ioutil.ReadFile(\"%s\")", filepath) |
|
} |
|
|
|
// Variables / Arguments |
|
// TODO replace this with a proper lexer so we can tell whether the var has a default or not |
|
matches := regexp.MustCompile("variable\\s+\"([^\"]+)\"\\s*{?").FindAllSubmatch(bytes, -1) |
|
for _, match := range matches { |
|
if len(match) < 2 { |
|
return nil, errors.New("can't parseModulesFolder because something unexpected happened parsing terraform code") |
|
} |
|
variableName := string(match[1]) |
|
module.Arguments = append(module.Arguments, &tfVariable{ |
|
Name: variableName, |
|
IsList: strings.HasSuffix(variableName, "list"), |
|
}) |
|
} |
|
|
|
// Outputs / Attributes |
|
matches = regexp.MustCompile("output\\s+\"([^\"]+)\"\\s*{?").FindAllSubmatch(bytes, -1) |
|
for _, match := range matches { |
|
if len(match) < 2 { |
|
return nil, errors.New("can't parseModulesFolder because something unexpected happened parsing terraform code") |
|
} |
|
variableName := string(match[1]) |
|
module.Attributes = append(module.Attributes, &tfVariable{ |
|
Name: variableName, |
|
IsList: strings.HasSuffix(variableName, "list"), |
|
}) |
|
} |
|
} |
|
} |
|
|
|
modules = append(modules, module) |
|
|
|
} |
|
} |
|
|
|
return modules, nil |
|
} |
|
|
|
func getPostToObjectStorageShellScript( |
|
config *configuration.Configuration, |
|
credentials []configuration.Credential, |
|
) (string, error) { |
|
|
|
backblazeTemplate := ` |
|
BUCKET_NAME="%s" |
|
AUTH_JSON="$(curl -sS -u "%s:%s" https://api.backblazeb2.com/b2api/v2/b2_authorize_account)" |
|
|
|
API_URL="$(echo "$AUTH_JSON" | grep -E -o '"apiUrl": "([^"]+)"' | sed -E 's|"apiUrl": "([^"]+)"|\1|')" |
|
ACCOUNT_ID="$(echo "$AUTH_JSON" | grep -E -o '"accountId": "([^"]+)"' | sed -E 's|"accountId": "([^"]+)"|\1|')" |
|
AUTH_TOKEN="$(echo "$AUTH_JSON" | grep -E -o '"authorizationToken": "([^"]+)"' | sed -E 's|"authorizationToken": "([^"]+)"|\1|')" |
|
|
|
LIST_BUCKETS_JSON="$(curl -sS -H "Authorization: $AUTH_TOKEN" "$API_URL/b2api/v2/b2_list_buckets?accountId=$ACCOUNT_ID&bucketName=$BUCKET_NAME" )" |
|
BUCKET_ID="$(echo "$LIST_BUCKETS_JSON" | grep -E -o '"bucketId": "([^"]+)"' | sed -E 's|"bucketId": "([^"]+)"|\1|')" |
|
|
|
UPLOAD_URL_JSON="$(curl -sS -H "Authorization: $AUTH_TOKEN" "$API_URL/b2api/v2/b2_get_upload_url?bucketId=$BUCKET_ID" )" |
|
|
|
UPLOAD_URL="$(echo "$UPLOAD_URL_JSON" | grep -E -o '"uploadUrl": "([^"]+)"' | sed -E 's|"uploadUrl": "([^"]+)"|\1|')" |
|
AUTH_TOKEN="$(echo "$UPLOAD_URL_JSON" | grep -E -o '"authorizationToken": "([^"]+)"' | sed -E 's|"authorizationToken": "([^"]+)"|\1|')" |
|
|
|
CONTENT_SHA1="$(echo -n "$CONTENT" | sha1sum | awk '{ print $1 }')" |
|
|
|
curl -sS -X POST \ |
|
-H "Authorization: $AUTH_TOKEN" \ |
|
-H "X-Bz-File-Name: $FILE_PATH" \ |
|
-H "X-Bz-Content-Sha1: $CONTENT_SHA1" \ |
|
-H "Content-Type: text/plain" \ |
|
"$UPLOAD_URL" -d "$CONTENT" |
|
` |
|
scripts := []string{} |
|
|
|
for _, backend := range config.ObjectStorage.Backends { |
|
for _, credential := range credentials { |
|
if credential.Type == backend.Provider { |
|
if backend.Provider == configuration.BACKBLAZE_B2 { |
|
scripts = append(scripts, fmt.Sprintf(backblazeTemplate, backend.Name, credential.Username, credential.Password)) |
|
} |
|
if backend.Provider == configuration.AMAZON_S3 { |
|
// TODO |
|
return "", errors.New("getPostToObjectStorageShellScript not implemented yet for S3-compatible") |
|
} |
|
} |
|
} |
|
} |
|
|
|
if len(scripts) == 0 { |
|
return "", errors.New("No credentials were passed to getPostToObjectStorageShellScript") |
|
} |
|
|
|
return strings.Join(scripts, "\n"), nil |
|
} |
|
|
|
func getSSHPublicKeys(workingDirectory string) ([][]string, error) { |
|
// TODO Make sure these keys only readable by root |
|
sshDirectory := filepath.Join(workingDirectory, configuration.SSH_KEYS_PATH) |
|
sshFileInfos, err := ioutil.ReadDir(sshDirectory) |
|
if err != nil { |
|
return [][]string{}, err |
|
} |
|
toReturn := [][]string{} |
|
for _, fileInfo := range sshFileInfos { |
|
filepath := filepath.Join(sshDirectory, fileInfo.Name()) |
|
if !fileInfo.IsDir() && strings.HasSuffix(filepath, ".pub") { |
|
|
|
// todo scrape the name from the key comment ?? |
|
name := strings.TrimSuffix(fileInfo.Name(), ".pub") |
|
toReturn = append(toReturn, []string{filepath, name}) |
|
} |
|
} |
|
return toReturn, nil |
|
}
|
|
|