github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/common/state.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  
    11  	"github.com/juju/errors"
    12  	goyaml "gopkg.in/yaml.v2"
    13  
    14  	"github.com/juju/juju/core/instance"
    15  	"github.com/juju/juju/environs"
    16  	"github.com/juju/juju/environs/storage"
    17  )
    18  
    19  // StateFile is the name of the file where the provider's state is stored.
    20  const StateFile = "provider-state"
    21  
    22  // BootstrapState is the state information that is stored in StateFile.
    23  //
    24  // Individual providers may define their own state structures instead of
    25  // this one, and use their own code for loading and saving those, but this is
    26  // the definition that most practically useful providers share unchanged.
    27  type BootstrapState struct {
    28  	// StateInstances are the controllers.
    29  	StateInstances []instance.Id `yaml:"state-instances"`
    30  }
    31  
    32  // putState writes the given data to the state file on the given storage.
    33  // The file's name is as defined in StateFile.
    34  func putState(stor storage.StorageWriter, data []byte) error {
    35  	logger.Debugf("putting %q to bootstrap storage %T", StateFile, stor)
    36  	return stor.Put(StateFile, bytes.NewBuffer(data), int64(len(data)))
    37  }
    38  
    39  // CreateStateFile creates an empty state file on the given storage, and
    40  // returns its URL.
    41  func CreateStateFile(stor storage.Storage) (string, error) {
    42  	err := putState(stor, []byte{})
    43  	if err != nil {
    44  		return "", fmt.Errorf("cannot create initial state file: %v", err)
    45  	}
    46  	return stor.URL(StateFile)
    47  }
    48  
    49  // DeleteStateFile deletes the state file on the given storage.
    50  func DeleteStateFile(stor storage.Storage) error {
    51  	return stor.Remove(StateFile)
    52  }
    53  
    54  // SaveState writes the given state to the given storage.
    55  func SaveState(storage storage.StorageWriter, state *BootstrapState) error {
    56  	data, err := goyaml.Marshal(state)
    57  	if err != nil {
    58  		return err
    59  	}
    60  	return putState(storage, data)
    61  }
    62  
    63  // LoadState reads state from the given storage.
    64  func LoadState(stor storage.StorageReader) (*BootstrapState, error) {
    65  	r, err := storage.Get(stor, StateFile)
    66  	if err != nil {
    67  		if errors.IsNotFound(err) {
    68  			return nil, environs.ErrNotBootstrapped
    69  		}
    70  		return nil, err
    71  	}
    72  	return loadState(r)
    73  }
    74  
    75  func loadState(r io.ReadCloser) (*BootstrapState, error) {
    76  	defer r.Close()
    77  	data, err := io.ReadAll(r)
    78  	if err != nil {
    79  		return nil, fmt.Errorf("error reading %q: %v", StateFile, err)
    80  	}
    81  	var state BootstrapState
    82  	err = goyaml.Unmarshal(data, &state)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("error unmarshalling %q: %v", StateFile, err)
    85  	}
    86  	return &state, nil
    87  }
    88  
    89  // AddStateInstance adds a controller instance ID to the provider-state
    90  // file in storage.
    91  func AddStateInstance(stor storage.Storage, id instance.Id) error {
    92  	state, err := LoadState(stor)
    93  	if err == environs.ErrNotBootstrapped {
    94  		state = &BootstrapState{}
    95  	} else if err != nil {
    96  		return errors.Annotate(err, "cannot record state instance-id")
    97  	}
    98  	state.StateInstances = append(state.StateInstances, id)
    99  	return SaveState(stor, state)
   100  }
   101  
   102  // RemoveStateInstances removes controller instance IDs from the
   103  // provider-state file in storage. Instance IDs that are not found
   104  // in the file are ignored.
   105  func RemoveStateInstances(stor storage.Storage, ids ...instance.Id) error {
   106  	state, err := LoadState(stor)
   107  	if err == environs.ErrNotBootstrapped {
   108  		return nil
   109  	} else if err != nil {
   110  		return errors.Annotate(err, "cannot remove recorded state instance-id")
   111  	}
   112  	var anyFound bool
   113  	for i := 0; i < len(state.StateInstances); i++ {
   114  		for _, id := range ids {
   115  			if state.StateInstances[i] == id {
   116  				head := state.StateInstances[:i]
   117  				tail := state.StateInstances[i+1:]
   118  				state.StateInstances = append(head, tail...)
   119  				anyFound = true
   120  				i--
   121  				break
   122  			}
   123  		}
   124  	}
   125  	if !anyFound {
   126  		return nil
   127  	}
   128  	return SaveState(stor, state)
   129  }
   130  
   131  // ProviderStateInstances extracts the instance IDs from provider-state.
   132  func ProviderStateInstances(stor storage.StorageReader) ([]instance.Id, error) {
   133  	st, err := LoadState(stor)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	return st.StateInstances, nil
   138  }