Documentation
¶
Overview ¶
Package spec parses, validates, and manipulates Deployah spec files.
It loads YAML specs, resolves environments and env files, applies schema defaults, substitutes variables, and validates against embedded JSON schemas.
Loading and saving ¶
Validation ¶
- ValidateSpec: validate spec data against a schema version
- ValidateEnvironments: validate environment definitions
- ValidateSpecComponents: check component resources and autoscaling
Defaults ¶
- FillSpecWithDefaults: apply schema defaults to a Spec
- CreateSpecWithDefaults: create a new spec with defaults
Index ¶
- Constants
- Variables
- func ClearSchemaCache()
- func FillSpecWithDefaults(spec *Spec, version string) error
- func ParseDuration(s string) (int, error)
- func Save(spec *Spec, path string) error
- func SubstituteVariables(data []byte, env *Environment) ([]byte, error)
- func ValidateAPIVersion(specObj map[string]any) (string, error)
- func ValidateComponentAutoscaling(component Component) error
- func ValidateComponentHealth(component Component) error
- func ValidateComponentName(name string) error
- func ValidateComponentResources(component Component) error
- func ValidateEnvName(name string) error
- func ValidateEnvVarName(name string) error
- func ValidateEnvironments(specObj map[string]any, version string) error
- func ValidateHostname(hostname string) error
- func ValidatePort(portStr string) error
- func ValidateProjectName(name string) error
- func ValidateSpec(specObj map[string]any, version string) error
- func ValidateSpecComponents(spec *Spec) error
- type Autoscaling
- type Component
- type ComponentKind
- type ComponentRole
- type DefaultValues
- type Environment
- type Health
- type HealthAlive
- type HealthReady
- type Ingress
- type Metric
- type MetricType
- type ResourcePreset
- type Resources
- type Spec
Examples ¶
Constants ¶
const ( // DefaultSpecPath is the default path for the Deployah spec file DefaultSpecPath = "deployah.yaml" // DefaultEnvFile is the default environment file name DefaultEnvFile = ".env" // DefaultConfigFile is the default configuration file name DefaultConfigFile = "config.yaml" // DeployahConfigDir is the default directory for Deployah-specific files DeployahConfigDir = ".deployah" // EnvFilePrefix is the prefix for environment-specific files EnvFilePrefix = ".env." // ConfigFilePrefix is the prefix for environment-specific config files ConfigFilePrefix = "config." // ConfigFileSuffix is the suffix for configuration files ConfigFileSuffix = ".yaml" )
File and Path Constants
const ( // EnvVarPrefix is the prefix for Deployah-specific environment variables EnvVarPrefix = "DPY_VAR_" // LogLevelEnvVar is the environment variable for log level override LogLevelEnvVar = "DPY_LOG_LEVEL" )
Environment Variables
const ( // MaxComponentNameLength is the maximum allowed length for component names MaxComponentNameLength = 63 // MaxProjectNameLength is the maximum allowed length for project names MaxProjectNameLength = 63 // MaxEnvironmentNameLength is the maximum allowed length for environment names MaxEnvironmentNameLength = 63 // ComponentNamePattern is the regex pattern for valid component names ComponentNamePattern = "^[a-zA-Z0-9_-]+$" // ProjectNamePattern is the regex pattern for valid project names ProjectNamePattern = "^[a-zA-Z0-9_-]+$" // EnvironmentNamePattern is the regex pattern for valid environment names EnvironmentNamePattern = "^[a-zA-Z0-9_-]+$" )
Validation Constants
const ( // PlaceholderName is the placeholder used in templates for name substitution PlaceholderName = "{name}" // ComponentsPrefix is the prefix for component paths in schemas ComponentsPrefix = "components." // EnvironmentsPrefix is the prefix for environment paths in schemas EnvironmentsPrefix = "environments." // ArrayItemIndexTemplate is the template for array item indices in schema paths ArrayItemIndexTemplate = "[0]" // EnvFileSuffix is the suffix to remove from environment names during cleanup EnvFileSuffix = "/*" )
Spec Processing
const ( // DefaultStartupProbePeriod is how often (in seconds) the startup probe // checks the container port during the startup window. DefaultStartupProbePeriod = 5 // DefaultStartupProbeFailureThreshold is how many consecutive failures // before the container is killed during startup. // Budget: 36 * 5s = 180s (3 minutes). DefaultStartupProbeFailureThreshold = 36 // DefaultStartupProbeTimeout is the per-request timeout in seconds for // the startup probe. DefaultStartupProbeTimeout = 3 // DefaultReadinessProbePeriod is how often (in seconds) the readiness // probe checks whether the container can receive traffic. DefaultReadinessProbePeriod = 5 // DefaultReadinessProbeFailureThreshold is how many consecutive failures // before the container is removed from service endpoints. // Detection window: 3 * 5s = 15s. DefaultReadinessProbeFailureThreshold = 3 // DefaultReadinessProbeTimeout is the per-request timeout in seconds for // the readiness probe. DefaultReadinessProbeTimeout = 3 // DefaultLivenessProbePeriod is how often (in seconds) the alive probe // checks whether the container is responsive. DefaultLivenessProbePeriod = 10 // DefaultLivenessProbeTimeout is the per-request timeout in seconds for // the alive probe. DefaultLivenessProbeTimeout = 3 // DefaultLivenessRestartAfterSec is the default restart-after window // in seconds (used as a numeric fallback in probe generation). DefaultLivenessRestartAfterSec = 60 // DefaultLivenessInterval is the default value for health.alive.interval // when the field is omitted. DefaultLivenessInterval = "10s" // DefaultLivenessRestartAfter is the default value for // health.alive.restartAfter when the field is omitted. DefaultLivenessRestartAfter = "60s" )
Health Check Probe Timing
These constants define the Kubernetes probe parameters used when building startup, readiness, and liveness probes from the spec health fields. They are named constants so that the product behavior (e.g. how quickly a pod is removed from rotation) can be reviewed and changed in one place.
const ( // DefaultResourcePreset is the default resource preset when none is specified DefaultResourcePreset = "small" // MinCPUMillicores is the minimum CPU allocation in millicores MinCPUMillicores = 10 // MaxCPUMillicores is the maximum CPU allocation in millicores MaxCPUMillicores = 16000 // MinMemoryMB is the minimum memory allocation in megabytes MinMemoryMB = 16 // MaxMemoryMB is the maximum memory allocation in megabytes MaxMemoryMB = 32768 )
Resource Management
const ( // LabelPrefix is the prefix for all Deployah-managed labels LabelPrefix = "deployah.dev" // LabelProject is the label key for project identification LabelProject = LabelPrefix + "/project" // LabelEnvironment is the label key for environment identification LabelEnvironment = LabelPrefix + "/environment" // LabelManagedBy is the label key indicating management by Deployah LabelManagedBy = LabelPrefix + "/managed-by" // LabelVersion is the label key for API version tracking LabelVersion = LabelPrefix + "/version" // LabelComponent is the label key for component identification LabelComponent = LabelPrefix + "/component" // ManagedByValue is the value used for the managed-by label ManagedByValue = "deployah" )
Kubernetes Labels
Variables ¶
var ResourcePresetMappings = map[ResourcePreset]map[string]Resources{ ResourcePresetNano: { "requests": { CPU: new("100m"), Memory: new("128Mi"), EphemeralStorage: new("50Mi"), }, "limits": { CPU: new("150m"), Memory: new("192Mi"), EphemeralStorage: new("2Gi"), }, }, ResourcePresetMicro: { "requests": { CPU: new("250m"), Memory: new("256Mi"), EphemeralStorage: new("50Mi"), }, "limits": { CPU: new("375m"), Memory: new("384Mi"), EphemeralStorage: new("2Gi"), }, }, ResourcePresetSmall: { "requests": { CPU: new("500m"), Memory: new("512Mi"), EphemeralStorage: new("50Mi"), }, "limits": { CPU: new("750m"), Memory: new("768Mi"), EphemeralStorage: new("2Gi"), }, }, ResourcePresetMedium: { "requests": { CPU: new("500m"), Memory: new("1024Mi"), EphemeralStorage: new("50Mi"), }, "limits": { CPU: new("750m"), Memory: new("1536Mi"), EphemeralStorage: new("2Gi"), }, }, ResourcePresetLarge: { "requests": { CPU: new("1000m"), Memory: new("2048Mi"), EphemeralStorage: new("50Mi"), }, "limits": { CPU: new("1500m"), Memory: new("3072Mi"), EphemeralStorage: new("2Gi"), }, }, ResourcePresetXLarge: { "requests": { CPU: new("1000m"), Memory: new("3072Mi"), EphemeralStorage: new("50Mi"), }, "limits": { CPU: new("3000m"), Memory: new("6144Mi"), EphemeralStorage: new("2Gi"), }, }, ResourcePreset2XLarge: { "requests": { CPU: new("1000m"), Memory: new("3072Mi"), EphemeralStorage: new("50Mi"), }, "limits": { CPU: new("6000m"), Memory: new("12288Mi"), EphemeralStorage: new("2Gi"), }, }, }
ResourcePresetMappings defines the resource specifications for each preset
Functions ¶
func ClearSchemaCache ¶
func ClearSchemaCache()
ClearSchemaCache clears all cached schemas and patterns. Call it between tests that mutate schema caches, or to release memory in long-running processes.
Example usage in tests:
func TestSchemaDefaults(t *testing.T) {
defer ClearSchemaCache() // Clean up after test
// ... test code that modifies schemas
}
func FillSpecWithDefaults ¶
FillSpecWithDefaults fills a Spec with defaults from JSON schemas. It applies spec and environment schema defaults, resolves resource presets, and supports environment-specific placeholder substitution.
The function processes defaults in this order:
- Apply spec schema defaults to components
- Resolve resource presets to concrete values
- Merge spec and environment defaults
- Apply merged defaults to environments with placeholder substitution
spec is updated in place. version selects the schema version for default extraction. Returns an error if schema loading, default extraction, or application fails.
Example:
spec := &Spec{
APIVersion: "v1-alpha.1",
Project: "my-app",
Components: map[string]Component{
"web": {Image: "nginx:latest"}, // Only image specified
},
Environments: []Environment{
{Name: "production"}, // Only name specified
},
}
err := FillSpecWithDefaults(spec, "v1-alpha.1")
// After filling:
// spec.Components["web"].Role = "service"
// spec.Components["web"].Port = 8080
// spec.Environments[0].EnvFile = ".env.production"
Example ¶
ExampleFillSpecWithDefaults applies schema defaults to a minimal manifest.
package main
import (
"fmt"
"log"
"deployah.dev/deployah/internal/spec"
)
func main() {
m := &spec.Spec{
APIVersion: "v1-alpha.1",
Project: "demo",
Components: map[string]spec.Component{
"web": {Image: "nginx:latest"},
},
}
if err := spec.FillSpecWithDefaults(m, "v1-alpha.1"); err != nil {
log.Fatal(err)
}
fmt.Println(m.Components["web"].Port)
}
Output: 8080
func ParseDuration ¶
ParseDuration converts a deployah duration string into whole seconds.
It delegates to time.ParseDuration, so it accepts the same syntax (e.g. "10s", "2m", "1h"). The JSON Schema pattern on health.alive fields already constrains values to positive integer seconds, minutes, or hours, so sub-second units and float values are rejected before they reach this function.
Zero and negative durations are rejected.
func SubstituteVariables ¶
func SubstituteVariables(data []byte, env *Environment) ([]byte, error)
SubstituteVariables substitutes variables in spec data using the provided environment. Variable precedence is lowest to highest: environment definition, env file, then OS environment variables. Substitution uses the envsubst syntax.
func ValidateAPIVersion ¶
ValidateAPIVersion checks the spec apiVersion field for presence, type, and validity. Returns the apiVersion string if valid, or an error otherwise.
func ValidateComponentAutoscaling ¶
ValidateComponentAutoscaling validates a component's autoscaling configuration.
func ValidateComponentHealth ¶
ValidateComponentHealth validates the health check configuration of a component. Health checks are only supported for role: service components.
func ValidateComponentName ¶
ValidateComponentName validates a component name against the JSON schema pattern
func ValidateComponentResources ¶
ValidateComponentResources validates a component's resource configuration. A component can have either: 1. Explicit resources (resources field with actual values) 2. Resource preset (resourcePreset field) 3. Neither (will use defaults) It cannot have both resources and resourcePreset, or an empty resources object.
func ValidateEnvName ¶
ValidateEnvName validates an environment name against the JSON schema pattern
func ValidateEnvVarName ¶
ValidateEnvVarName validates an environment variable name against the JSON schema pattern.
func ValidateEnvironments ¶
ValidateEnvironments validates environments YAML against the provided JSON schema file. version should be the version of the schema (e.g., "v1-alpha.1"). This is a strict validation: unknown fields are not allowed.
func ValidateHostname ¶
ValidateHostname validates a hostname against the JSON schema pattern
func ValidatePort ¶
ValidatePort validates that the port is a number between 1024 and 65535.
func ValidateProjectName ¶
ValidateProjectName validates a project name against the JSON schema pattern
func ValidateSpec ¶
ValidateSpec validates spec YAML against the provided JSON schema. version should be the version of the schema (e.g., "v1-alpha.1"). This is a strict validation: unknown fields are not allowed.
func ValidateSpecComponents ¶
ValidateSpecComponents validates all components in a spec.
Types ¶
type Autoscaling ¶
type Autoscaling struct {
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"`
MinReplicas int `json:"minReplicas,omitempty" yaml:"minReplicas,omitempty"`
MaxReplicas int `json:"maxReplicas,omitempty" yaml:"maxReplicas,omitempty"`
Metrics []Metric `json:"metrics,omitempty" yaml:"metrics,omitempty"`
}
Autoscaling defines the autoscaling settings for the component.
type Component ¶
type Component struct {
// Role selects the default deployment strategy for the component.
Role ComponentRole `json:"role,omitempty" yaml:"role,omitempty"`
// EnvFile is the path to a component-specific dotenv file.
EnvFile string `json:"envFile,omitempty" yaml:"envFile,omitempty"`
// ConfigFile is the path to a component-specific config file.
ConfigFile string `json:"configFile,omitempty" yaml:"configFile,omitempty"`
// Environments limits the component to the named environments.
Environments []string `json:"environments,omitempty" yaml:"environments,omitempty"`
// Kind selects stateless or stateful deployment behavior.
Kind ComponentKind `json:"kind,omitempty" yaml:"kind,omitempty"`
// Image is the container image reference.
Image string `json:"image" yaml:"image"`
// Command overrides the container entrypoint.
Command []string `json:"command,omitempty" yaml:"command,omitempty"`
// Args overrides the container command arguments.
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
// Port is the primary container port for services.
Port int `json:"port,omitempty" yaml:"port,omitempty"`
// Autoscaling configures horizontal pod autoscaling.
Autoscaling *Autoscaling `json:"autoscaling,omitempty" yaml:"autoscaling,omitempty"`
// Resources sets explicit CPU, memory, and storage requests and limits.
Resources Resources `json:"resources" yaml:"resources,omitempty"`
// ResourcePreset selects a named resource profile when Resources is empty.
ResourcePreset ResourcePreset `json:"resourcePreset,omitempty" yaml:"resourcePreset,omitempty"`
// Ingress exposes the component through an HTTP or HTTPS route.
Ingress *Ingress `json:"ingress,omitempty" yaml:"ingress,omitempty"`
// Env sets static environment variables for the container.
Env map[string]string `json:"env,omitempty" yaml:"env,omitempty"`
// Health configures ready and alive checks for the component.
Health *Health `json:"health,omitempty" yaml:"health,omitempty"`
}
Component defines a deployable unit in the project.
type ComponentKind ¶
type ComponentKind string
ComponentKind specifies the kind of the component.
const ( // ComponentKindStateless runs replicas that do not require stable storage. ComponentKindStateless ComponentKind = "stateless" // ComponentKindStateful runs replicas that require stable storage. ComponentKindStateful ComponentKind = "stateful" )
type ComponentRole ¶
type ComponentRole string
ComponentRole defines the role of a component and its default deployment strategy.
const ( // ComponentRoleService runs a long-lived HTTP or network service. ComponentRoleService ComponentRole = "service" // ComponentRoleWorker runs background or queue-processing workloads. ComponentRoleWorker ComponentRole = "worker" // ComponentRoleJob runs a finite batch or one-off task. ComponentRoleJob ComponentRole = "job" )
type DefaultValues ¶
DefaultValues represents default values extracted from a JSON schema. Map keys are dot-notation paths to fields; values are defaults to apply.
Examples of keys:
- "components.[^[a-zA-Z0-9_-]+$].role" -> "service" (pattern-based component default)
- "components.web.port" -> 8080 (specific component default)
- "environments.[0].envFile" -> ".env.{name}" (environment template with placeholder)
- "environments.production.configFile" -> "config.production.yaml" (specific environment)
func GetDefaultValues ¶
func GetDefaultValues(version string, schemaType schema.SchemaType) (DefaultValues, error)
GetDefaultValues extracts all default values from a JSON schema. This is the main entry point for schema-based defaults for a version and type.
version is the schema version identifier (e.g. "v1-alpha.1"). schemaType selects the spec or environments schema. Returns a map of dot-notation paths to default values, or an error if schema loading or processing fails.
Example usage:
defaults, err := GetDefaultValues("v1-alpha.1", schema.SchemaTypeManifest)
if err != nil {
return err
}
// defaults now contains:
// "components.[^[a-zA-Z0-9_-]+$].role" -> "service"
// "components.[^[a-zA-Z0-9_-]+$].port" -> 8080
// etc.
type Environment ¶
type Environment struct {
// Name is the environment identifier (e.g. "staging").
Name string `json:"name" yaml:"name"`
// EnvFile is the path to a dotenv file for this environment.
EnvFile string `json:"envFile,omitempty" yaml:"envFile,omitempty"`
// ConfigFile is the path to an environment-specific config file.
ConfigFile string `json:"configFile,omitempty" yaml:"configFile,omitempty"`
// Context is the Kubernetes context to use for this environment.
Context string `json:"context,omitempty" yaml:"context,omitempty"`
// Variables holds inline key-value overrides for this environment.
Variables map[string]string `json:"variables,omitempty" yaml:"variables,omitempty"`
}
Environment defines a named deployment target and its configuration sources.
func ResolveEnvironment ¶
func ResolveEnvironment(environments []Environment, desiredEnvironment string) (*Environment, error)
ResolveEnvironment returns the environment by name, or the default if name is empty. Returns an error if not found or if multiple environments are defined but none specified.
type Health ¶
type Health struct {
// Ready controls the readiness check. Provide a path to upgrade from TCP
// to HTTP. Set to false to disable readiness and startup checks entirely.
Ready *HealthReady `json:"ready,omitempty" yaml:"ready,omitempty"`
// Alive controls the alive check. Provide a path to upgrade from TCP to
// HTTP. Set to false to disable the alive check entirely.
Alive *HealthAlive `json:"alive,omitempty" yaml:"alive,omitempty"`
}
Health configures HTTP health checks for a service component. When omitted, TCP checks on the component port run automatically.
type HealthAlive ¶
type HealthAlive struct {
// Disabled is true when the developer set alive: false.
Disabled bool `json:"-" yaml:"-"`
// Path is the HTTP endpoint that must return 2xx for the pod to be
// considered alive. Must start with /. Check only internal process
// state here, not external dependencies.
Path string `json:"path,omitempty" yaml:"path,omitempty"`
// Interval is how often to check the endpoint (e.g. "10s", "1m").
// Defaults to "10s" when omitted.
Interval string `json:"interval,omitempty" yaml:"interval,omitempty"`
// RestartAfter is how long the endpoint must fail continuously before
// the pod is restarted (e.g. "60s", "2m"). Defaults to "60s" when
// omitted. The effective window rounds up to the nearest multiple of
// Interval.
RestartAfter string `json:"restartAfter,omitempty" yaml:"restartAfter,omitempty"`
}
HealthAlive configures the alive check for a service component. It accepts either false (to disable) or an object with a path and optional timing.
When Alive is nil (field absent), a TCP alive check on the component port runs automatically.
func (*HealthAlive) UnmarshalJSON ¶
func (a *HealthAlive) UnmarshalJSON(data []byte) error
UnmarshalJSON handles both false and object forms:
alive: false -> HealthAlive{Disabled: true}
alive: {path: /livez, interval: 10s} -> HealthAlive{Path: "/livez", ...}
type HealthReady ¶
type HealthReady struct {
// Disabled is true when the developer set ready: false.
Disabled bool `json:"-" yaml:"-"`
// Path is the HTTP endpoint that must return 2xx for the component to
// receive traffic. Must start with /.
Path string `json:"path,omitempty" yaml:"path,omitempty"`
}
HealthReady configures the readiness check for a service component. It accepts either false (to disable) or an object with a path.
When Ready is nil (field absent), a TCP readiness check on the component port runs automatically.
func (*HealthReady) UnmarshalJSON ¶
func (r *HealthReady) UnmarshalJSON(data []byte) error
UnmarshalJSON handles both false and object forms:
ready: false -> HealthReady{Disabled: true}
ready: {path: /h} -> HealthReady{Path: "/h"}
type Metric ¶
type Metric struct {
Type MetricType `json:"type" yaml:"type"`
Target int `json:"target" yaml:"target"`
}
Metric defines a metric used to trigger autoscaling.
type MetricType ¶
type MetricType string
MetricType specifies the type of metric to monitor.
const ( // MetricTypeCPU scales on CPU utilization. MetricTypeCPU MetricType = "cpu" // MetricTypeMemory scales on memory utilization. MetricTypeMemory MetricType = "memory" )
type ResourcePreset ¶
type ResourcePreset string
ResourcePreset specifies the resource preset for the component.
const ( // ResourcePresetNano is the smallest resource preset. ResourcePresetNano ResourcePreset = "nano" // ResourcePresetMicro is a very small resource preset. ResourcePresetMicro ResourcePreset = "micro" // ResourcePresetSmall is a small resource preset. ResourcePresetSmall ResourcePreset = "small" // ResourcePresetMedium is a medium resource preset. ResourcePresetMedium ResourcePreset = "medium" // ResourcePresetLarge is a large resource preset. ResourcePresetLarge ResourcePreset = "large" // ResourcePresetXLarge is an extra-large resource preset. ResourcePresetXLarge ResourcePreset = "xlarge" // ResourcePreset2XLarge is a double extra-large resource preset. ResourcePreset2XLarge ResourcePreset = "2xlarge" )
type Resources ¶
type Resources struct {
CPU *string `json:"cpu,omitempty" yaml:"cpu,omitempty"`
Memory *string `json:"memory,omitempty" yaml:"memory,omitempty"`
EphemeralStorage *string `json:"ephemeralStorage,omitempty" yaml:"ephemeralStorage,omitempty"`
}
Resources defines the resource requests and limits for the component.
type Spec ¶
type Spec struct {
// APIVersion is the schema version of the spec (e.g., "v1-alpha.1").
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
// Project is the project name.
Project string `json:"project" yaml:"project"`
// Environments is a list of environment definitions.
Environments []Environment `json:"environments,omitempty" yaml:"environments,omitempty"`
// Components is a map of component names to their configuration.
Components map[string]Component `json:"components" yaml:"components"`
}
Spec defines the structure of the project spec.
func CreateSpecWithDefaults ¶
CreateSpecWithDefaults creates a new Spec with default values applied. This is a convenience function that creates a minimal spec structure and then applies all relevant defaults from the specified schema version.
projectName names the new project. version selects the schema version for defaults. Returns a new spec with defaults applied, or an error if creation or default application fails.
Example:
spec, err := CreateSpecWithDefaults("my-app", "v1-alpha.1")
// Returns:
// &Spec{
// APIVersion: "v1-alpha.1",
// Project: "my-app",
// Components: map[string]Component{}, // Empty but initialized
// }
func Load ¶
Load reads and parses the spec YAML file at the given path, resolves the environment (using the provided envName or default resolution rules), and substitutes variables according to precedence (environment definition, env file, then OS environment). Returns the parsed Spec or an error.
Example ¶
ExampleLoad reads a manifest file from disk.
package main
import (
"context"
"fmt"
"log"
"os"
"deployah.dev/deployah/internal/spec"
)
func main() {
const yamlDoc = `apiVersion: v1-alpha.1
project: demo
environments:
- name: default
components:
web:
image: nginx:latest
resources:
cpu: 500m
memory: 512Mi
`
f, err := os.CreateTemp("", "example-*.yaml")
if err != nil {
log.Fatal(err)
}
path := f.Name()
if _, err = f.WriteString(yamlDoc); err != nil {
if rmErr := os.Remove(path); rmErr != nil {
log.Print(rmErr)
}
log.Fatal(err)
}
if err = f.Close(); err != nil {
if rmErr := os.Remove(path); rmErr != nil {
log.Print(rmErr)
}
log.Fatal(err)
}
m, err := spec.Load(context.Background(), path, "")
if err != nil {
if rmErr := os.Remove(path); rmErr != nil {
log.Print(rmErr)
}
log.Fatal(err)
}
defer func() {
if rmErr := os.Remove(path); rmErr != nil {
log.Print(rmErr)
}
}()
fmt.Println(m.Project)
}
Output: demo
func (*Spec) EnvironmentNames ¶
EnvironmentNames returns the list of environment names defined in the spec. Returns an empty slice if no environments are defined.